@pasko70/pibo 1.7.10 → 1.7.12
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/apps/chat/chat-request-normalizers.js +1 -1
- package/dist/apps/chat/project-workflow-sessions.js +1 -1
- package/dist/apps/chat/ralph-api.js +1 -1
- package/dist/apps/chat-ui/assets/{dist-D6ImmtZ3.js → dist--lraqdDn.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CCOZbHCt.js → dist-6pcjNbPQ.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Daom6uMW.js → dist-CMFjl7MX.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DTJJ5-iA.js → dist-COlFTw2x.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DDQWCHTh.js → dist-CZSEM5So.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Dgs6LwMW.js → dist-CajLLpaw.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Bg2k47fY.js → dist-CoCvyd4f.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BLYDOjGT.js → dist-Dgz3iWay.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BLQTYmgi.js → dist-IlQbTNzw.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Bv912fTl.js → dist-YdEosvsr.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-LRUZKp77.js → dist-bLd-NaVv.js} +1 -1
- package/dist/apps/chat-ui/assets/{index-BnZ0V5cJ.js → index-HPWlrJwv.js} +3 -3
- package/dist/apps/chat-ui/index.html +1 -1
- package/dist/apps/chat-vscode-web/assets/{index-CiQZTRZH.js → index-Dge6XEYB.js} +1 -1
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/apps/cli-ui/InkSessionApp.js +3 -3
- package/dist/apps/cli-ui/cliSessionsCommand.js +1 -1
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.7.12.vsix +0 -0
- package/dist/cli-session/fakeSessionSource.js +2 -2
- package/dist/cli.js +1 -1
- package/dist/core/routed-session.js +7 -0
- package/dist/core/runtime-telemetry.js +5 -5
- package/dist/core/runtime.js +22 -0
- package/dist/core/session-errors.js +16 -0
- package/dist/core/session-router.js +9 -0
- package/dist/core/thinking.js +1 -1
- package/dist/gateway/cli.js +35 -16
- package/dist/gateway/pidfile.js +90 -44
- package/dist/gateway/server.js +22 -16
- package/dist/gateway/web.js +29 -23
- package/dist/providers/openai-gpt56.js +2 -2
- package/dist/ralph/cli.js +2 -2
- package/dist/ralph/service.js +60 -20
- package/dist/ralph/store.js +2 -2
- package/dist/session-ui/commandCatalog.js +1 -1
- package/dist/tools/guides.js +1 -1
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta name="theme-color" content="#101d22" />
|
|
7
7
|
<title>Pibo</title>
|
|
8
|
-
<script type="module" crossorigin src="/apps/chat-vscode/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/apps/chat-vscode/assets/index-Dge6XEYB.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/apps/chat-vscode/assets/index-B5QK07zO.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
@@ -699,8 +699,8 @@ async function executeSharedSlashCommand(command, source, state, setState, openS
|
|
|
699
699
|
setState((current) => applyCommandResultToState(current, command, result.descriptor, status));
|
|
700
700
|
return true;
|
|
701
701
|
}
|
|
702
|
-
const THINKING_LEVELS = ["current/default", "off", "minimal", "low", "medium", "high", "xhigh"];
|
|
703
|
-
const APPLY_THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh"]);
|
|
702
|
+
const THINKING_LEVELS = ["current/default", "off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
703
|
+
const APPLY_THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
704
704
|
async function selectCommandMenuItem(picker, item, source, state, setState, openSession) {
|
|
705
705
|
if (item.disabled) {
|
|
706
706
|
setState((current) => ({ ...current, message: item.description ? `${item.label}: ${item.description}` : `${item.label} is unavailable.`, error: undefined }));
|
|
@@ -787,7 +787,7 @@ function thinkingPickerState() {
|
|
|
787
787
|
function validateThinkingLevel(value) {
|
|
788
788
|
const level = value.trim().toLowerCase();
|
|
789
789
|
if (!APPLY_THINKING_LEVELS.has(level))
|
|
790
|
-
throw new Error(`Unsupported thinking level "${value}". Use off, minimal, low, medium, high, or
|
|
790
|
+
throw new Error(`Unsupported thinking level "${value}". Use off, minimal, low, medium, high, xhigh, or max.`);
|
|
791
791
|
}
|
|
792
792
|
function modelProviderPickerState(providers) {
|
|
793
793
|
return {
|
|
@@ -148,7 +148,7 @@ class DebugMockCliSessionRouter {
|
|
|
148
148
|
const level = typeof params.level === "string" ? params.level : undefined;
|
|
149
149
|
if (level)
|
|
150
150
|
return { message: `Thinking level set to ${level}`, level, supported: true, changed: true };
|
|
151
|
-
return { action: "show_thinking_menu", items: ["off", "minimal", "low", "medium", "high", "xhigh"].map((value) => ({ id: value, label: value })) };
|
|
151
|
+
return { action: "show_thinking_menu", items: ["off", "minimal", "low", "medium", "high", "xhigh", "max"].map((value) => ({ id: value, label: value })) };
|
|
152
152
|
}
|
|
153
153
|
if (event.action === "model") {
|
|
154
154
|
const model = typeof params.model === "string" ? params.model : undefined;
|
|
Binary file
|
|
Binary file
|
|
@@ -343,9 +343,9 @@ export class FakeCliSessionSource {
|
|
|
343
343
|
if (!level)
|
|
344
344
|
return {
|
|
345
345
|
action: "show_thinking_menu",
|
|
346
|
-
items: ["off", "minimal", "low", "medium", "high", "xhigh"].map((value) => ({ id: value, label: value })),
|
|
346
|
+
items: ["off", "minimal", "low", "medium", "high", "xhigh", "max"].map((value) => ({ id: value, label: value })),
|
|
347
347
|
};
|
|
348
|
-
if (!["off", "minimal", "low", "medium", "high", "xhigh"].includes(level))
|
|
348
|
+
if (!["off", "minimal", "low", "medium", "high", "xhigh", "max"].includes(level))
|
|
349
349
|
return {
|
|
350
350
|
supported: false,
|
|
351
351
|
unsupportedReason: `Unsupported thinking level ${level}.`,
|
package/dist/cli.js
CHANGED
|
@@ -338,7 +338,7 @@ export async function runPiboCli(argv = process.argv) {
|
|
|
338
338
|
.command("tui:routed")
|
|
339
339
|
.helpOption("-h, --help", "Display help for command")
|
|
340
340
|
.option("--show-thinking", "Show routed thinking deltas in the local TUI")
|
|
341
|
-
.option("--thinking <level>", "Set routed thinking level: off, minimal, low, medium, high, xhigh", parsePiboThinkingLevel)
|
|
341
|
+
.option("--thinking <level>", "Set routed thinking level: off, minimal, low, medium, high, xhigh, max", parsePiboThinkingLevel)
|
|
342
342
|
.argument("[profile]")
|
|
343
343
|
.description("Start the local routed Pibo TUI")
|
|
344
344
|
.action(async (profile, options) => {
|
|
@@ -565,6 +565,7 @@ export class RoutedSession {
|
|
|
565
565
|
getStatus() {
|
|
566
566
|
const enabledTools = this.runtime.session.getActiveToolNames();
|
|
567
567
|
const thinkingLevel = this.runtime.session.thinkingLevel;
|
|
568
|
+
const settingsManager = this.runtime.session.settingsManager;
|
|
568
569
|
return {
|
|
569
570
|
piboSessionId: this.piboSessionId,
|
|
570
571
|
queuedMessages: this.queue.length,
|
|
@@ -576,6 +577,12 @@ export class RoutedSession {
|
|
|
576
577
|
disposed: this.disposed,
|
|
577
578
|
thinkingLevel,
|
|
578
579
|
fastMode: this.getFastModeResult().mode === "fast",
|
|
580
|
+
...(settingsManager ? {
|
|
581
|
+
retry: {
|
|
582
|
+
...settingsManager.getRetrySettings(),
|
|
583
|
+
provider: settingsManager.getProviderRetrySettings(),
|
|
584
|
+
},
|
|
585
|
+
} : {}),
|
|
579
586
|
};
|
|
580
587
|
}
|
|
581
588
|
getContextUsage() {
|
|
@@ -68,7 +68,7 @@ export class PiboRuntimeTelemetryRecorder {
|
|
|
68
68
|
this.recordToolExecutionFinished(event, context);
|
|
69
69
|
return;
|
|
70
70
|
case "session_error":
|
|
71
|
-
this.recordTurnTerminal(event, context, "error", "error", event.error);
|
|
71
|
+
this.recordTurnTerminal(event, context, "error", "error", event.error, event.errorDetails?.category ?? event.errorDetails?.errorClass);
|
|
72
72
|
return;
|
|
73
73
|
case "execution_result":
|
|
74
74
|
this.recordExecutionResult(event, context);
|
|
@@ -368,13 +368,13 @@ export class PiboRuntimeTelemetryRecorder {
|
|
|
368
368
|
return;
|
|
369
369
|
this.startOrProgressPhase(turn, phaseName, new Date().toISOString(), summary, { updateTurn: true });
|
|
370
370
|
}
|
|
371
|
-
recordTurnTerminal(event, context, status, phaseName, summary) {
|
|
371
|
+
recordTurnTerminal(event, context, status, phaseName, summary, errorCategory) {
|
|
372
372
|
const turn = this.turnContextForEvent(event.piboSessionId, event.eventId, undefined, context) ?? this.activeTurnContext(event.piboSessionId, context);
|
|
373
373
|
if (!turn)
|
|
374
374
|
return;
|
|
375
375
|
const now = new Date().toISOString();
|
|
376
376
|
this.finishOpenPhases(turn.turnId, terminalPhaseStatus(status), now);
|
|
377
|
-
this.finishActiveProviderRequests(turn.turnId, providerStatusForTurnStatus(status), now, summary);
|
|
377
|
+
this.finishActiveProviderRequests(turn.turnId, providerStatusForTurnStatus(status), now, summary, errorCategory);
|
|
378
378
|
this.finishActiveToolCalls(turn.turnId, status, now, summary);
|
|
379
379
|
this.telemetry.upsertPhase({
|
|
380
380
|
phaseId: phaseId(turn.turnId, phaseName),
|
|
@@ -486,7 +486,7 @@ export class PiboRuntimeTelemetryRecorder {
|
|
|
486
486
|
normalizedEventCount: request.normalizedEventCount + 1,
|
|
487
487
|
});
|
|
488
488
|
}
|
|
489
|
-
finishActiveProviderRequests(turnId, status, now, summary) {
|
|
489
|
+
finishActiveProviderRequests(turnId, status, now, summary, errorCategory) {
|
|
490
490
|
const timeline = this.store?.getTurnTimeline(turnId, { limit: 100 });
|
|
491
491
|
for (const request of timeline?.providerRequests ?? []) {
|
|
492
492
|
if (isTerminalProviderStatus(request.status))
|
|
@@ -494,7 +494,7 @@ export class PiboRuntimeTelemetryRecorder {
|
|
|
494
494
|
this.upsertProviderRequestFromExisting(request, {
|
|
495
495
|
status,
|
|
496
496
|
completedAt: now,
|
|
497
|
-
errorCategory: status === "error" ? "runtime_error" : undefined,
|
|
497
|
+
errorCategory: status === "error" ? errorCategory ?? "runtime_error" : undefined,
|
|
498
498
|
errorMessage: status === "error" ? safeSummary(summary) : undefined,
|
|
499
499
|
});
|
|
500
500
|
}
|
package/dist/core/runtime.js
CHANGED
|
@@ -25,6 +25,27 @@ import { PIBO_APP_CONTEXT } from "../app-context.js";
|
|
|
25
25
|
import { createRuntimeToolDefinition } from "../tools/runtime/tool.js";
|
|
26
26
|
import { RuntimeSessionRegistry } from "../tools/runtime/registry.js";
|
|
27
27
|
import { compactValidationToolResultForContext } from "./test-output-compaction.js";
|
|
28
|
+
function hasOwnRetrySetting(settings, key) {
|
|
29
|
+
return settings !== undefined && settings !== null && Object.prototype.hasOwnProperty.call(settings, key);
|
|
30
|
+
}
|
|
31
|
+
export function applyPiboRuntimeRetryDefaults(settingsManager, defaults) {
|
|
32
|
+
if (!defaults)
|
|
33
|
+
return;
|
|
34
|
+
const globalRetry = settingsManager.getGlobalSettings().retry;
|
|
35
|
+
const projectRetry = settingsManager.getProjectSettings().retry;
|
|
36
|
+
const overrides = {};
|
|
37
|
+
if (!hasOwnRetrySetting(globalRetry, "enabled") && !hasOwnRetrySetting(projectRetry, "enabled") && defaults.enabled !== undefined) {
|
|
38
|
+
overrides.enabled = defaults.enabled;
|
|
39
|
+
}
|
|
40
|
+
if (!hasOwnRetrySetting(globalRetry, "maxRetries") && !hasOwnRetrySetting(projectRetry, "maxRetries") && defaults.maxRetries !== undefined) {
|
|
41
|
+
overrides.maxRetries = defaults.maxRetries;
|
|
42
|
+
}
|
|
43
|
+
if (!hasOwnRetrySetting(globalRetry, "baseDelayMs") && !hasOwnRetrySetting(projectRetry, "baseDelayMs") && defaults.baseDelayMs !== undefined) {
|
|
44
|
+
overrides.baseDelayMs = defaults.baseDelayMs;
|
|
45
|
+
}
|
|
46
|
+
if (Object.keys(overrides).length > 0)
|
|
47
|
+
settingsManager.applyOverrides({ retry: overrides });
|
|
48
|
+
}
|
|
28
49
|
function resolveProfilePath(cwd, path) {
|
|
29
50
|
return isAbsolute(path) ? path : resolve(cwd, path);
|
|
30
51
|
}
|
|
@@ -243,6 +264,7 @@ export async function createPiboRuntime(options = {}) {
|
|
|
243
264
|
}),
|
|
244
265
|
},
|
|
245
266
|
});
|
|
267
|
+
applyPiboRuntimeRetryDefaults(services.settingsManager, options.retryDefaults);
|
|
246
268
|
registerOpenAiGpt56Models(services.modelRegistry);
|
|
247
269
|
registerMiniMaxProvider(services.modelRegistry);
|
|
248
270
|
registerGlmProvider(services.modelRegistry);
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
const PROVIDER_NETWORK_ERROR_MARKERS = [
|
|
2
|
+
"fetch failed",
|
|
3
|
+
"network error",
|
|
4
|
+
"connection error",
|
|
5
|
+
"connection refused",
|
|
6
|
+
"connection lost",
|
|
7
|
+
"connection reset",
|
|
8
|
+
"other side closed",
|
|
9
|
+
"upstream connect",
|
|
10
|
+
"reset before headers",
|
|
11
|
+
"socket hang up",
|
|
12
|
+
"socket connection was closed",
|
|
13
|
+
];
|
|
1
14
|
export function classifySessionErrorMessage(message, options = {}) {
|
|
2
15
|
const normalized = message.toLowerCase();
|
|
3
16
|
if (normalized.includes("context_length_exceeded") || normalized.includes("context window")) {
|
|
@@ -18,6 +31,9 @@ export function classifySessionErrorMessage(message, options = {}) {
|
|
|
18
31
|
if (normalized.includes("timeout") || normalized.includes("timed out")) {
|
|
19
32
|
return { category: "provider_transport", errorClass: "provider_transport", code: "timeout", origin: "provider", retryable: true, userMessage: "The provider request timed out." };
|
|
20
33
|
}
|
|
34
|
+
if (PROVIDER_NETWORK_ERROR_MARKERS.some((marker) => normalized.includes(marker))) {
|
|
35
|
+
return { category: "provider_transport", errorClass: "provider_transport", code: "network_error", origin: "provider", retryable: true, userMessage: "The provider network connection failed." };
|
|
36
|
+
}
|
|
21
37
|
if (/\b5\d\d\b/.test(normalized)) {
|
|
22
38
|
return { category: "provider_server", errorClass: "provider_server", code: "provider_server_error", origin: "provider", retryable: true, userMessage: "The provider returned a server error." };
|
|
23
39
|
}
|
|
@@ -20,6 +20,14 @@ import { withWorkflowSessionKind } from "../sessions/workflow-session-kind.js";
|
|
|
20
20
|
import { PiboRuntimeTelemetryRecorder } from "./runtime-telemetry.js";
|
|
21
21
|
import { createPiboProviderTelemetryExtension } from "./provider-telemetry.js";
|
|
22
22
|
const DEFAULT_SUBAGENT_REPLY_TIMEOUT_MS = 10 * 60 * 1000;
|
|
23
|
+
export const RALPH_RUNTIME_RETRY_DEFAULTS = {
|
|
24
|
+
enabled: true,
|
|
25
|
+
maxRetries: 7,
|
|
26
|
+
baseDelayMs: 2_000,
|
|
27
|
+
};
|
|
28
|
+
export function resolvePiboSessionRetryDefaults(kind, configured) {
|
|
29
|
+
return configured ?? (kind === "ralph" ? RALPH_RUNTIME_RETRY_DEFAULTS : undefined);
|
|
30
|
+
}
|
|
23
31
|
export function resolvePiboSessionInitialThinkingLevel(session) {
|
|
24
32
|
const value = session.metadata?.initialThinkingLevel;
|
|
25
33
|
return typeof value === "string" && isPiboThinkingLevel(value) ? value : undefined;
|
|
@@ -360,6 +368,7 @@ export class PiboSessionRouter {
|
|
|
360
368
|
cwd: piboSession.workspace ?? this.options.cwd,
|
|
361
369
|
persistSession: this.options.persistSession,
|
|
362
370
|
thinkingLevel: initialThinkingLevel ?? this.options.thinkingLevel,
|
|
371
|
+
retryDefaults: resolvePiboSessionRetryDefaults(piboSession.kind, this.options.retryDefaults),
|
|
363
372
|
profile: profileForSession(profile, piboSession.piSessionId, parentPiSessionId),
|
|
364
373
|
extensionFactories: [
|
|
365
374
|
...(telemetryExtension ? [telemetryExtension] : []),
|
package/dist/core/thinking.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const PIBO_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"];
|
|
1
|
+
export const PIBO_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
2
2
|
export function isPiboThinkingLevel(value) {
|
|
3
3
|
return PIBO_THINKING_LEVELS.includes(value);
|
|
4
4
|
}
|
package/dist/gateway/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn, execFile } from "node:child_process";
|
|
2
|
-
import { existsSync, mkdirSync, openSync, readFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, openSync, readFileSync, readdirSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
@@ -87,28 +87,40 @@ function resolveGatewayWebCommand(argv, target) {
|
|
|
87
87
|
];
|
|
88
88
|
return { command: process.execPath, args };
|
|
89
89
|
}
|
|
90
|
-
function
|
|
91
|
-
|
|
90
|
+
function managedGatewayPidPaths(target) {
|
|
91
|
+
const home = managedGatewayHome(target);
|
|
92
|
+
try {
|
|
93
|
+
const legacy = readdirSync(home)
|
|
94
|
+
.filter((name) => /^gateway-\d+\.pid$/.test(name))
|
|
95
|
+
.map((name) => join(home, name));
|
|
96
|
+
return [join(home, "gateway.pid"), ...legacy];
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return [join(home, "gateway.pid")];
|
|
100
|
+
}
|
|
92
101
|
}
|
|
93
102
|
function readManagedGatewayPid(target) {
|
|
94
|
-
|
|
95
|
-
const path = managedGatewayPidPath(target);
|
|
96
|
-
if (!existsSync(path))
|
|
97
|
-
return undefined;
|
|
98
|
-
const pid = Number(readFileSync(path, "utf-8").trim());
|
|
99
|
-
if (!Number.isInteger(pid) || pid <= 0)
|
|
100
|
-
return undefined;
|
|
103
|
+
for (const path of managedGatewayPidPaths(target)) {
|
|
101
104
|
try {
|
|
102
|
-
|
|
103
|
-
|
|
105
|
+
if (!existsSync(path))
|
|
106
|
+
continue;
|
|
107
|
+
const pid = Number(readFileSync(path, "utf-8").trim());
|
|
108
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
109
|
+
continue;
|
|
110
|
+
try {
|
|
111
|
+
process.kill(pid, 0);
|
|
112
|
+
return pid;
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
if (error.code === "EPERM")
|
|
116
|
+
return pid;
|
|
117
|
+
}
|
|
104
118
|
}
|
|
105
119
|
catch {
|
|
106
|
-
|
|
120
|
+
// Try the next current or legacy PID file.
|
|
107
121
|
}
|
|
108
122
|
}
|
|
109
|
-
|
|
110
|
-
return undefined;
|
|
111
|
-
}
|
|
123
|
+
return undefined;
|
|
112
124
|
}
|
|
113
125
|
async function waitForTargetGatewayDown(target, maxRetries = 40, intervalMs = 250) {
|
|
114
126
|
const port = targetPort(target);
|
|
@@ -322,6 +334,13 @@ async function runManagedGatewayCommand(target, command, args, argv = process.ar
|
|
|
322
334
|
process.exitCode = 1;
|
|
323
335
|
return true;
|
|
324
336
|
}
|
|
337
|
+
const existingPid = readManagedGatewayPid(target);
|
|
338
|
+
if (existingPid !== undefined) {
|
|
339
|
+
console.error(`Start blocked: ${managedGatewayHome(target)} is already owned by gateway PID ${existingPid}.`);
|
|
340
|
+
console.error(`The configured status port ${targetPort(target)} is not reachable; check the gateway port configuration instead of starting a second gateway with the same PIBO_HOME.`);
|
|
341
|
+
process.exitCode = 1;
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
325
344
|
console.error(`Starting ${target === "web" ? "production" : "dev"} gateway...`);
|
|
326
345
|
try {
|
|
327
346
|
await runGatewayManager("start", target, argv);
|
package/dist/gateway/pidfile.js
CHANGED
|
@@ -1,84 +1,130 @@
|
|
|
1
|
-
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { getPiboHome, piboHomePath } from "../core/pibo-home.js";
|
|
4
|
+
function gatewayPidPath() {
|
|
5
|
+
return piboHomePath("gateway.pid");
|
|
5
6
|
}
|
|
6
7
|
function fallbackGatewayPidPath() {
|
|
7
8
|
return piboHomePath("gateway-fallback.pid");
|
|
8
9
|
}
|
|
9
|
-
|
|
10
|
+
function legacyGatewayPidPaths() {
|
|
11
|
+
try {
|
|
12
|
+
const home = getPiboHome();
|
|
13
|
+
return readdirSync(home)
|
|
14
|
+
.filter((name) => /^gateway-\d+\.pid$/.test(name))
|
|
15
|
+
.map((name) => join(home, name));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function storedPid(path) {
|
|
10
22
|
try {
|
|
11
|
-
const path = gatewayPidPath(port);
|
|
12
23
|
if (!existsSync(path))
|
|
13
24
|
return undefined;
|
|
14
25
|
const pid = parseInt(readFileSync(path, "utf-8").trim(), 10);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
26
|
+
return Number.isNaN(pid) ? undefined : pid;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function livePid(path) {
|
|
33
|
+
const pid = storedPid(path);
|
|
34
|
+
if (pid === undefined)
|
|
35
|
+
return undefined;
|
|
36
|
+
try {
|
|
37
|
+
process.kill(pid, 0);
|
|
38
|
+
return pid;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (error.code === "EPERM")
|
|
19
42
|
return pid;
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function claimPidFile(path, label) {
|
|
47
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
48
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
49
|
+
try {
|
|
50
|
+
writeFileSync(path, String(process.pid), { encoding: "utf-8", flag: "wx" });
|
|
51
|
+
return;
|
|
20
52
|
}
|
|
21
|
-
catch {
|
|
22
|
-
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (error.code !== "EEXIST")
|
|
55
|
+
throw error;
|
|
56
|
+
const existingPid = livePid(path);
|
|
57
|
+
if (existingPid === process.pid)
|
|
58
|
+
return;
|
|
59
|
+
if (existingPid !== undefined)
|
|
60
|
+
throw new Error(`${label} already running (PID ${existingPid})`);
|
|
61
|
+
try {
|
|
62
|
+
unlinkSync(path);
|
|
63
|
+
}
|
|
64
|
+
catch (unlinkError) {
|
|
65
|
+
if (unlinkError.code !== "ENOENT")
|
|
66
|
+
throw unlinkError;
|
|
67
|
+
}
|
|
23
68
|
}
|
|
24
69
|
}
|
|
70
|
+
throw new Error(`Unable to claim ${label.toLowerCase()} PID file`);
|
|
71
|
+
}
|
|
72
|
+
function clearPidFileIfOwned(path) {
|
|
73
|
+
try {
|
|
74
|
+
if (storedPid(path) === process.pid)
|
|
75
|
+
unlinkSync(path);
|
|
76
|
+
}
|
|
25
77
|
catch {
|
|
26
|
-
|
|
78
|
+
// ignore
|
|
27
79
|
}
|
|
28
80
|
}
|
|
29
|
-
export function
|
|
81
|
+
export function readPidFile() {
|
|
82
|
+
return livePid(gatewayPidPath()) ?? legacyGatewayPidPaths().map(livePid).find((pid) => pid !== undefined);
|
|
83
|
+
}
|
|
84
|
+
export function clearPidFile() {
|
|
30
85
|
try {
|
|
31
|
-
const path = gatewayPidPath(
|
|
32
|
-
if (existsSync(path))
|
|
86
|
+
const path = gatewayPidPath();
|
|
87
|
+
if (existsSync(path))
|
|
33
88
|
unlinkSync(path);
|
|
34
|
-
}
|
|
35
89
|
}
|
|
36
90
|
catch {
|
|
37
91
|
// ignore
|
|
38
92
|
}
|
|
39
93
|
}
|
|
40
|
-
export function
|
|
41
|
-
|
|
94
|
+
export function releaseGatewayPid() {
|
|
95
|
+
clearPidFileIfOwned(gatewayPidPath());
|
|
96
|
+
}
|
|
97
|
+
export function writeGatewayPid() {
|
|
98
|
+
const existingPid = readPidFile();
|
|
42
99
|
if (existingPid !== undefined && existingPid !== process.pid) {
|
|
43
100
|
throw new Error(`Gateway already running (PID ${existingPid})`);
|
|
44
101
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
const path = fallbackGatewayPidPath();
|
|
50
|
-
if (!existsSync(path))
|
|
51
|
-
return undefined;
|
|
52
|
-
const pid = parseInt(readFileSync(path, "utf-8").trim(), 10);
|
|
53
|
-
if (Number.isNaN(pid))
|
|
54
|
-
return undefined;
|
|
102
|
+
for (const path of legacyGatewayPidPaths()) {
|
|
103
|
+
if (livePid(path) !== undefined)
|
|
104
|
+
continue;
|
|
55
105
|
try {
|
|
56
|
-
|
|
57
|
-
return pid;
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
return undefined;
|
|
106
|
+
unlinkSync(path);
|
|
61
107
|
}
|
|
108
|
+
catch { }
|
|
62
109
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
110
|
+
claimPidFile(gatewayPidPath(), "Gateway");
|
|
111
|
+
}
|
|
112
|
+
export function readFallbackPidFile() {
|
|
113
|
+
return livePid(fallbackGatewayPidPath());
|
|
66
114
|
}
|
|
67
115
|
export function clearFallbackPidFile() {
|
|
68
116
|
try {
|
|
69
117
|
const path = fallbackGatewayPidPath();
|
|
70
|
-
if (existsSync(path))
|
|
118
|
+
if (existsSync(path))
|
|
71
119
|
unlinkSync(path);
|
|
72
|
-
}
|
|
73
120
|
}
|
|
74
121
|
catch {
|
|
75
122
|
// ignore
|
|
76
123
|
}
|
|
77
124
|
}
|
|
125
|
+
export function releaseFallbackGatewayPid() {
|
|
126
|
+
clearPidFileIfOwned(fallbackGatewayPidPath());
|
|
127
|
+
}
|
|
78
128
|
export function writeFallbackGatewayPid() {
|
|
79
|
-
|
|
80
|
-
if (existingPid !== undefined && existingPid !== process.pid) {
|
|
81
|
-
throw new Error(`Fallback gateway already running (PID ${existingPid})`);
|
|
82
|
-
}
|
|
83
|
-
writeFileSync(fallbackGatewayPidPath(), String(process.pid), "utf-8");
|
|
129
|
+
claimPidFile(fallbackGatewayPidPath(), "Fallback gateway");
|
|
84
130
|
}
|
package/dist/gateway/server.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createDefaultPiboPluginRegistry, createPiboProfileFromRegistryOrDefault
|
|
|
3
3
|
import { PiboSessionRouter } from "../core/session-router.js";
|
|
4
4
|
import { loadPiboModelDefaults, selectRequestedModelProfile } from "../core/model-defaults.js";
|
|
5
5
|
import { DEFAULT_GATEWAY_HOST, DEFAULT_GATEWAY_PORT, encodeFrame, errorResponse, isGatewayRequestFrame, isGatewaySubscribeFrame, } from "./protocol.js";
|
|
6
|
-
import {
|
|
6
|
+
import { releaseFallbackGatewayPid, releaseGatewayPid, writeFallbackGatewayPid, writeGatewayPid } from "./pidfile.js";
|
|
7
7
|
const DEFAULT_MAX_BACKPRESSURE_FRAMES = 1_000;
|
|
8
8
|
const DEFAULT_MAX_BACKPRESSURE_BYTES = 4 * 1024 * 1024;
|
|
9
9
|
function parseJsonLine(line) {
|
|
@@ -342,31 +342,37 @@ export class PiboGatewayServer {
|
|
|
342
342
|
}
|
|
343
343
|
}
|
|
344
344
|
export async function runGatewayServer(options = {}) {
|
|
345
|
-
const
|
|
346
|
-
await server.start();
|
|
345
|
+
const fallbackMode = process.env.PIBO_FALLBACK_MODE === "1";
|
|
347
346
|
try {
|
|
348
|
-
if (
|
|
347
|
+
if (fallbackMode)
|
|
349
348
|
writeFallbackGatewayPid();
|
|
350
|
-
|
|
351
|
-
else {
|
|
349
|
+
else
|
|
352
350
|
writeGatewayPid();
|
|
353
|
-
}
|
|
354
351
|
}
|
|
355
|
-
catch (
|
|
356
|
-
console.error(
|
|
357
|
-
|
|
358
|
-
|
|
352
|
+
catch (error) {
|
|
353
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
354
|
+
process.exitCode = 1;
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const releasePid = fallbackMode ? releaseFallbackGatewayPid : releaseGatewayPid;
|
|
358
|
+
let server;
|
|
359
|
+
try {
|
|
360
|
+
server = new PiboGatewayServer(options);
|
|
361
|
+
await server.start();
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
releasePid();
|
|
365
|
+
throw error;
|
|
359
366
|
}
|
|
360
367
|
const host = options.host ?? DEFAULT_GATEWAY_HOST;
|
|
361
368
|
const port = options.port ?? DEFAULT_GATEWAY_PORT;
|
|
362
369
|
console.error(`pibo gateway listening on ${host}:${port}`);
|
|
363
370
|
const stop = async () => {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
clearFallbackPidFile();
|
|
371
|
+
try {
|
|
372
|
+
await server.stop();
|
|
367
373
|
}
|
|
368
|
-
|
|
369
|
-
|
|
374
|
+
finally {
|
|
375
|
+
releasePid();
|
|
370
376
|
}
|
|
371
377
|
};
|
|
372
378
|
process.once("SIGINT", () => {
|
package/dist/gateway/web.js
CHANGED
|
@@ -13,7 +13,7 @@ import { createPiboWebHostPlugin } from "../plugins/web.js";
|
|
|
13
13
|
import { DEFAULT_WEB_CHANNEL_HOST, DEFAULT_WEB_CHANNEL_PORT } from "../web/channel.js";
|
|
14
14
|
import { loadPiboConfig } from "../config/config.js";
|
|
15
15
|
import { PiboGatewayServer } from "./server.js";
|
|
16
|
-
import {
|
|
16
|
+
import { releaseFallbackGatewayPid, releaseGatewayPid, writeFallbackGatewayPid, writeGatewayPid } from "./pidfile.js";
|
|
17
17
|
const PUBLIC_WEB_CHANNEL_HOST = "0.0.0.0";
|
|
18
18
|
const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
|
|
19
19
|
function isComputeWorkerRuntime() {
|
|
@@ -175,37 +175,43 @@ function createChatAppURL(options, host, port) {
|
|
|
175
175
|
return `http://${host}:${port}/apps/chat`;
|
|
176
176
|
}
|
|
177
177
|
export async function runWebGatewayServer(options = {}) {
|
|
178
|
-
const
|
|
179
|
-
const pluginRegistry = resolvedOptions.pluginRegistry ?? createWebPiboPluginRegistry(resolvedOptions);
|
|
180
|
-
const pidFilePort = resolvedOptions.port;
|
|
181
|
-
const server = new PiboGatewayServer({
|
|
182
|
-
...resolvedOptions,
|
|
183
|
-
pluginRegistry,
|
|
184
|
-
});
|
|
185
|
-
await server.start();
|
|
178
|
+
const fallbackMode = process.env.PIBO_FALLBACK_MODE === "1";
|
|
186
179
|
try {
|
|
187
|
-
if (
|
|
180
|
+
if (fallbackMode)
|
|
188
181
|
writeFallbackGatewayPid();
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
writeGatewayPid(pidFilePort);
|
|
192
|
-
}
|
|
182
|
+
else
|
|
183
|
+
writeGatewayPid();
|
|
193
184
|
}
|
|
194
|
-
catch (
|
|
195
|
-
console.error(
|
|
196
|
-
|
|
197
|
-
|
|
185
|
+
catch (error) {
|
|
186
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
187
|
+
process.exitCode = 1;
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const releasePid = fallbackMode ? releaseFallbackGatewayPid : releaseGatewayPid;
|
|
191
|
+
let resolvedOptions;
|
|
192
|
+
let server;
|
|
193
|
+
try {
|
|
194
|
+
resolvedOptions = resolveWebGatewayServerOptions(options);
|
|
195
|
+
const pluginRegistry = resolvedOptions.pluginRegistry ?? createWebPiboPluginRegistry(resolvedOptions);
|
|
196
|
+
server = new PiboGatewayServer({
|
|
197
|
+
...resolvedOptions,
|
|
198
|
+
pluginRegistry,
|
|
199
|
+
});
|
|
200
|
+
await server.start();
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
releasePid();
|
|
204
|
+
throw error;
|
|
198
205
|
}
|
|
199
206
|
const host = resolvedOptions.web?.host ?? DEFAULT_WEB_CHANNEL_HOST;
|
|
200
207
|
const port = resolvedOptions.web?.port ?? DEFAULT_WEB_CHANNEL_PORT;
|
|
201
208
|
console.error(`pibo chat app available at ${createChatAppURL(resolvedOptions, host, port)}`);
|
|
202
209
|
const stop = async () => {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
clearFallbackPidFile();
|
|
210
|
+
try {
|
|
211
|
+
await server.stop();
|
|
206
212
|
}
|
|
207
|
-
|
|
208
|
-
|
|
213
|
+
finally {
|
|
214
|
+
releasePid();
|
|
209
215
|
}
|
|
210
216
|
};
|
|
211
217
|
process.once("SIGINT", () => {
|
|
@@ -45,7 +45,7 @@ export function buildOpenAiGpt56Models(baseModels = getBuiltInOpenAiModels()) {
|
|
|
45
45
|
api: OPENAI_RESPONSES_API,
|
|
46
46
|
baseUrl: OPENAI_BASE_URL,
|
|
47
47
|
contextWindow: OPENAI_GPT_56_CONTEXT_WINDOW,
|
|
48
|
-
thinkingLevelMap: { off: null, xhigh: "xhigh" },
|
|
48
|
+
thinkingLevelMap: { off: null, xhigh: "xhigh", max: "max" },
|
|
49
49
|
baseModels,
|
|
50
50
|
modelCost: (model) => model.cost,
|
|
51
51
|
});
|
|
@@ -56,7 +56,7 @@ export function buildOpenAiCodexGpt56Models(baseModels = getBuiltInOpenAiCodexMo
|
|
|
56
56
|
api: OPENAI_CODEX_RESPONSES_API,
|
|
57
57
|
baseUrl: OPENAI_CODEX_BASE_URL,
|
|
58
58
|
contextWindow: OPENAI_CODEX_GPT_56_CONTEXT_WINDOW,
|
|
59
|
-
thinkingLevelMap: { xhigh: "xhigh", minimal: "low" },
|
|
59
|
+
thinkingLevelMap: { xhigh: "xhigh", max: "max", minimal: "low" },
|
|
60
60
|
baseModels,
|
|
61
61
|
modelCost: (model) => ({ ...model.cost, cacheWrite: 0 }),
|
|
62
62
|
});
|