@chainingintention/pi-web-cn 1.202607.11 → 1.202608.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/dist/cli.js +27 -12
- package/dist/cli.js.map +1 -1
- package/dist/client/assets/{CodeViewer-WeDkBH1U.js → CodeViewer-j37b7YHz.js} +1 -1
- package/dist/client/assets/{TerminalPanel-DgiIksV-.js → TerminalPanel-DqDiXDrk.js} +5 -5
- package/dist/client/assets/{UnifiedDiffViewer-FKeWfK-h.js → UnifiedDiffViewer-AQTSNI1y.js} +1 -1
- package/dist/client/assets/index-DDuQX9_Y.js +4101 -0
- package/dist/client/index.html +1 -1
- package/dist/config.js +36 -0
- package/dist/config.js.map +1 -1
- package/dist/nativeServices/servicePlan.js +3 -2
- package/dist/nativeServices/servicePlan.js.map +1 -1
- package/dist/nativeServices/serviceProbe.js +7 -5
- package/dist/nativeServices/serviceProbe.js.map +1 -1
- package/dist/pi-web-plugins/info/infoInternals.js +223 -0
- package/dist/pi-web-plugins/info/pi-web-plugin.js +19 -22
- package/dist/pi-web-plugins/relays/markdownDocument.js +81 -0
- package/dist/pi-web-plugins/relays/package.json +9 -0
- package/dist/pi-web-plugins/relays/pi-web-plugin.js +44 -0
- package/dist/pi-web-plugins/relays/relayDiscovery.js +96 -0
- package/dist/pi-web-plugins/relays/relaysPanelElement.js +327 -0
- package/dist/pi-web-plugins/relays/vendor/README.md +24 -0
- package/dist/pi-web-plugins/relays/vendor/marked.esm.js +76 -0
- package/dist/pi-web-plugins/updates/pi-web-plugin.js +100 -100
- package/dist/pi-web-plugins/updates/updatesLogic.js +8 -5
- package/dist/plugin-api.d.ts +5 -1
- package/dist/server/configRoutes.js +11 -0
- package/dist/server/configRoutes.js.map +1 -1
- package/dist/server/diagnostics/nodePtyNativeModule.js +36 -0
- package/dist/server/diagnostics/nodePtyNativeModule.js.map +1 -0
- package/dist/server/git/gitService.js +176 -5
- package/dist/server/git/gitService.js.map +1 -1
- package/dist/server/piWebStatus.js +7 -4
- package/dist/server/piWebStatus.js.map +1 -1
- package/dist/server/realtime/sessionEventHub.js +16 -2
- package/dist/server/realtime/sessionEventHub.js.map +1 -1
- package/dist/server/sessiond/sessionDaemonStartup.js +0 -42
- package/dist/server/sessiond/sessionDaemonStartup.js.map +1 -1
- package/dist/server/sessiond/sessionServiceDependencies.js +31 -0
- package/dist/server/sessiond/sessionServiceDependencies.js.map +1 -0
- package/dist/server/sessions/askUserTool.js +105 -0
- package/dist/server/sessions/askUserTool.js.map +1 -0
- package/dist/server/sessions/authProviderOptions.js +26 -29
- package/dist/server/sessions/authProviderOptions.js.map +1 -1
- package/dist/server/sessions/authRoutes.js +9 -1
- package/dist/server/sessions/authRoutes.js.map +1 -1
- package/dist/server/sessions/authService.js +143 -33
- package/dist/server/sessions/authService.js.map +1 -1
- package/dist/server/sessions/globalProviderPolicy.js +167 -0
- package/dist/server/sessions/globalProviderPolicy.js.map +1 -0
- package/dist/server/sessions/modelCatalogRefresher.js +192 -0
- package/dist/server/sessions/modelCatalogRefresher.js.map +1 -0
- package/dist/server/sessions/oauthLoginFlowService.js +181 -56
- package/dist/server/sessions/oauthLoginFlowService.js.map +1 -1
- package/dist/server/sessions/parentSessionLocator.js +75 -0
- package/dist/server/sessions/parentSessionLocator.js.map +1 -0
- package/dist/server/sessions/pendingAskStore.js +278 -0
- package/dist/server/sessions/pendingAskStore.js.map +1 -0
- package/dist/server/sessions/piSessionService.js +1264 -199
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/sessions/plainTextTheme.js +112 -0
- package/dist/server/sessions/plainTextTheme.js.map +1 -0
- package/dist/server/sessions/projectAuthService.js +3 -3
- package/dist/server/sessions/projectAuthService.js.map +1 -1
- package/dist/server/sessions/sessionCommandService.js +60 -5
- package/dist/server/sessions/sessionCommandService.js.map +1 -1
- package/dist/server/sessions/sessionFileHeader.js +45 -0
- package/dist/server/sessions/sessionFileHeader.js.map +1 -0
- package/dist/server/sessions/sessionNotificationStore.js +484 -0
- package/dist/server/sessions/sessionNotificationStore.js.map +1 -0
- package/dist/server/sessions/sessionRoutes.js +245 -1
- package/dist/server/sessions/sessionRoutes.js.map +1 -1
- package/dist/server/sessions/sessionTreeProjection.js +250 -0
- package/dist/server/sessions/sessionTreeProjection.js.map +1 -0
- package/dist/server/sessions/sessionUnreadStore.js +520 -0
- package/dist/server/sessions/sessionUnreadStore.js.map +1 -0
- package/dist/server/sessions/spawnSubsessionTool.js +10 -12
- package/dist/server/sessions/spawnSubsessionTool.js.map +1 -1
- package/dist/server/sessions/spawnTargetResolver.js +3 -16
- package/dist/server/sessions/spawnTargetResolver.js.map +1 -1
- package/dist/server/terminals/terminalService.js +8 -2
- package/dist/server/terminals/terminalService.js.map +1 -1
- package/dist/server/workspaces/gitWorktreeDiscovery.js +9 -0
- package/dist/server/workspaces/gitWorktreeDiscovery.js.map +1 -1
- package/dist/server/workspaces/projectWorkspaceCwds.js +22 -0
- package/dist/server/workspaces/projectWorkspaceCwds.js.map +1 -0
- package/dist/server/workspaces/workspaceService.js +15 -2
- package/dist/server/workspaces/workspaceService.js.map +1 -1
- package/dist/shared/activity.js +9 -1
- package/dist/shared/activity.js.map +1 -1
- package/dist/shared/apiTypes.d.ts +403 -8
- package/dist/shared/apiTypes.js +26 -0
- package/dist/shared/apiTypes.js.map +1 -1
- package/dist/shared/capabilities.js +9 -0
- package/dist/shared/capabilities.js.map +1 -1
- package/dist/shared/federatedRoutes.js +12 -0
- package/dist/shared/federatedRoutes.js.map +1 -1
- package/docs/config.md +76 -9
- package/docs/plugins.md +72 -16
- package/install.sh +1 -1
- package/package.json +4 -2
- package/dist/client/assets/index-kgpXTDyy.js +0 -3247
- package/dist/server/sessions/sessionArchiveMigration.js +0 -590
- package/dist/server/sessions/sessionArchiveMigration.js.map +0 -1
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { isDeepStrictEqual } from "node:util";
|
|
5
|
+
import { createAgentSessionServices, } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
const LOG_CONTEXT = "global-provider-bootstrap";
|
|
7
|
+
const MODELS_FIELD = "models";
|
|
8
|
+
/**
|
|
9
|
+
* Snapshot the merged config Pi holds for every config-registered provider.
|
|
10
|
+
* Pi merges defined values over the previous registration, so the runtime's
|
|
11
|
+
* own record — not the extension's last argument — is the accurate baseline.
|
|
12
|
+
* Native providers have no comparable config and are deliberately absent.
|
|
13
|
+
*/
|
|
14
|
+
function captureProviderConfigBaseline(runtime) {
|
|
15
|
+
const baseline = new Map();
|
|
16
|
+
for (const providerId of runtime.getRegisteredProviderIds()) {
|
|
17
|
+
const config = runtime.getRegisteredProviderConfig(providerId);
|
|
18
|
+
if (config)
|
|
19
|
+
baseline.set(providerId, config);
|
|
20
|
+
}
|
|
21
|
+
return baseline;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* True when `incoming` would change the model catalog of `baseline` and nothing
|
|
25
|
+
* else.
|
|
26
|
+
*
|
|
27
|
+
* Extensions that refresh their catalog re-send a complete provider config, so
|
|
28
|
+
* the test is "equal to the baseline except for models", not "contains only
|
|
29
|
+
* models". Omitted fields are not changes: Pi's merge keeps the previous value,
|
|
30
|
+
* which also makes an unchanged catalog a plain replay rather than an update.
|
|
31
|
+
* Function-valued fields (`streamSimple`, `refreshModels`, `oauth` methods)
|
|
32
|
+
* compare by reference under deep strict equality, so a freshly created closure
|
|
33
|
+
* reads as a mismatch. That conservative direction is intentional: an unclear
|
|
34
|
+
* comparison must fall back to the frozen no-op.
|
|
35
|
+
*/
|
|
36
|
+
function isModelsOnlyProviderUpdate(baseline, incoming) {
|
|
37
|
+
const baselineFields = new Map(Object.entries(baseline));
|
|
38
|
+
const otherFieldsMatch = Object.entries(incoming).every(([field, value]) => {
|
|
39
|
+
if (field === MODELS_FIELD || value === undefined)
|
|
40
|
+
return true;
|
|
41
|
+
return isDeepStrictEqual(value, baselineFields.get(field));
|
|
42
|
+
});
|
|
43
|
+
if (!otherFieldsMatch)
|
|
44
|
+
return false;
|
|
45
|
+
return incoming.models !== undefined && !isDeepStrictEqual(incoming.models, baseline.models);
|
|
46
|
+
}
|
|
47
|
+
async function loadGlobalExtensionServices(runtime, agentDir) {
|
|
48
|
+
const scratchCwd = await mkdtemp(join(tmpdir(), "pi-web-global-ext-"));
|
|
49
|
+
try {
|
|
50
|
+
return await createAgentSessionServices({ cwd: scratchCwd, agentDir, modelRuntime: runtime });
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
await rm(scratchCwd, { recursive: true, force: true });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function logBootstrapDiagnostic(logger, diagnostic) {
|
|
57
|
+
const details = {
|
|
58
|
+
context: LOG_CONTEXT,
|
|
59
|
+
diagnosticType: diagnostic.type,
|
|
60
|
+
diagnostic: diagnostic.message,
|
|
61
|
+
};
|
|
62
|
+
if (diagnostic.type === "error") {
|
|
63
|
+
logger.error(details, "global extension provider bootstrap diagnostic");
|
|
64
|
+
}
|
|
65
|
+
else if (diagnostic.type === "warning") {
|
|
66
|
+
logger.warn(details, "global extension provider bootstrap diagnostic");
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
logger.info(details, "global extension provider bootstrap diagnostic");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function freezeProviderMutations(runtime, logger, configBaseline) {
|
|
73
|
+
const originalMethods = {
|
|
74
|
+
registerNativeProvider: runtime.registerNativeProvider.bind(runtime),
|
|
75
|
+
registerProvider: runtime.registerProvider.bind(runtime),
|
|
76
|
+
unregisterProvider: runtime.unregisterProvider.bind(runtime),
|
|
77
|
+
};
|
|
78
|
+
const loggedProviderIds = {
|
|
79
|
+
registerNativeProvider: new Set(),
|
|
80
|
+
registerProvider: new Set(),
|
|
81
|
+
unregisterProvider: new Set(),
|
|
82
|
+
};
|
|
83
|
+
// Logging must never turn a provider mutation into an extension failure.
|
|
84
|
+
const logQuietly = (details, message) => {
|
|
85
|
+
try {
|
|
86
|
+
logger.info(details, message);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Intentionally ignored; the mutation decision already stands.
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const logIgnoredMutation = (operation, providerId) => {
|
|
93
|
+
const loggedIds = loggedProviderIds[operation];
|
|
94
|
+
if (loggedIds.has(providerId))
|
|
95
|
+
return;
|
|
96
|
+
loggedIds.add(providerId);
|
|
97
|
+
logQuietly({ context: LOG_CONTEXT, operation, providerId }, "ignored provider mutation after global bootstrap");
|
|
98
|
+
};
|
|
99
|
+
const frozenMethods = {
|
|
100
|
+
registerProvider(providerId, config) {
|
|
101
|
+
const baseline = configBaseline.get(providerId);
|
|
102
|
+
if (!baseline || !isModelsOnlyProviderUpdate(baseline, config)) {
|
|
103
|
+
logIgnoredMutation("registerProvider", providerId);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// Pi validates the registration and ends in a fire-and-forget local
|
|
107
|
+
// refresh, so this stays synchronous and never reaches the network.
|
|
108
|
+
originalMethods.registerProvider(providerId, config);
|
|
109
|
+
const accepted = runtime.getRegisteredProviderConfig(providerId);
|
|
110
|
+
// Re-read the merged record so the next comparison uses what Pi stored.
|
|
111
|
+
if (accepted)
|
|
112
|
+
configBaseline.set(providerId, accepted);
|
|
113
|
+
logQuietly({
|
|
114
|
+
context: LOG_CONTEXT,
|
|
115
|
+
operation: "registerProvider",
|
|
116
|
+
providerId,
|
|
117
|
+
modelCount: accepted?.models?.length ?? 0,
|
|
118
|
+
}, "applied models-only provider update after global bootstrap");
|
|
119
|
+
},
|
|
120
|
+
registerNativeProvider(provider) {
|
|
121
|
+
logIgnoredMutation("registerNativeProvider", provider.id);
|
|
122
|
+
},
|
|
123
|
+
unregisterProvider(providerId) {
|
|
124
|
+
logIgnoredMutation("unregisterProvider", providerId);
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
try {
|
|
128
|
+
Object.assign(runtime, frozenMethods);
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
Object.assign(runtime, originalMethods);
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Load global extensions once against the shared model runtime, then make its
|
|
137
|
+
* extension-provider baseline immutable for the rest of the daemon lifetime.
|
|
138
|
+
* All sessions share this runtime, so accepting project-dependent mutations
|
|
139
|
+
* would leak provider configuration across workspaces. This is an accidental
|
|
140
|
+
* contamination guard, not a sandbox for otherwise trusted extensions.
|
|
141
|
+
*
|
|
142
|
+
* The temporary cwd is guaranteed to be empty, so Pi discovers agent-dir
|
|
143
|
+
* extensions without loading project resources. Documented initialization-time
|
|
144
|
+
* config and native registrations therefore reach the runtime through Pi's
|
|
145
|
+
* public service factory. Pi exposes no provider-freeze hook, so the daemon
|
|
146
|
+
* deliberately shadows the three public instance mutation methods afterward;
|
|
147
|
+
* every registration replay or later call is then a logged no-op.
|
|
148
|
+
*
|
|
149
|
+
* The one exception is a known config provider refreshing its own model
|
|
150
|
+
* catalog: a `registerProvider` call whose config matches the recorded
|
|
151
|
+
* baseline in every field except `models` is applied, because a catalog is a
|
|
152
|
+
* property of the provider rather than of the project. Native registration
|
|
153
|
+
* stays fully frozen — it passes a whole `Provider` object with no comparable
|
|
154
|
+
* config — as does unregistration.
|
|
155
|
+
*/
|
|
156
|
+
export async function bootstrapAndFreezeGlobalExtensionProviders(runtime, agentDir, logger) {
|
|
157
|
+
const services = await loadGlobalExtensionServices(runtime, agentDir);
|
|
158
|
+
const providerIds = Object.freeze([...runtime.getRegisteredProviderIds()].sort());
|
|
159
|
+
freezeProviderMutations(runtime, logger, captureProviderConfigBaseline(runtime));
|
|
160
|
+
for (const diagnostic of services.diagnostics)
|
|
161
|
+
logBootstrapDiagnostic(logger, diagnostic);
|
|
162
|
+
for (const extensionError of services.resourceLoader.getExtensions().errors) {
|
|
163
|
+
logger.error({ context: LOG_CONTEXT, error: extensionError.error }, "global extension failed during provider bootstrap");
|
|
164
|
+
}
|
|
165
|
+
logger.info({ context: LOG_CONTEXT, providerIds }, "global extension provider baseline bootstrapped and frozen");
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=globalProviderPolicy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"globalProviderPolicy.js","sourceRoot":"","sources":["../../../src/server/sessions/globalProviderPolicy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tick hourly and let pi decide when a fetch is actually due: pi treats stored
|
|
3
|
+
* catalogs as fresh for REMOTE_CATALOG_REFRESH_INTERVAL_MS (4h) and skips the
|
|
4
|
+
* network entirely on an unforced refresh inside that window. Ticking at
|
|
5
|
+
* exactly 4h would land a few seconds short of the window every time, because
|
|
6
|
+
* pi stamps `checkedAt` only after a fetch completes, so every other tick would
|
|
7
|
+
* be skipped and the real cadence would be ~8h. A shorter interval removes that
|
|
8
|
+
* off-by-one-latency skip, tolerates clock skew, and costs nothing when the
|
|
9
|
+
* cache is fresh, because scheduled runs never set `force`.
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_INTERVAL_MS = 60 * 60 * 1000;
|
|
12
|
+
/** Give the daemon a moment to finish startup before the first network refresh. */
|
|
13
|
+
const DEFAULT_INITIAL_DELAY_MS = 15_000;
|
|
14
|
+
/**
|
|
15
|
+
* Bound every catalog refresh so a stalled provider fetch cannot run forever.
|
|
16
|
+
* One run covers every refreshable provider under a single signal, so this is a
|
|
17
|
+
* whole-cycle budget for a background job, not pi's 15s startup budget.
|
|
18
|
+
*/
|
|
19
|
+
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
20
|
+
/**
|
|
21
|
+
* Wait this long before the single follow-up attempt that a timed-out or failed
|
|
22
|
+
* run earns, so a transient network problem does not cost a whole interval.
|
|
23
|
+
*/
|
|
24
|
+
const DEFAULT_RETRY_DELAY_MS = 5 * 60 * 1000;
|
|
25
|
+
/** A queued follow-up must keep the strongest mode requested while a run was in flight. */
|
|
26
|
+
const strongestMode = (left, right) => left === "forced" || right === "forced" ? "forced" : "scheduled";
|
|
27
|
+
const noopLogger = {
|
|
28
|
+
info() { },
|
|
29
|
+
warn() { },
|
|
30
|
+
error() { },
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Refreshes provider model catalogs over the network on a background schedule.
|
|
34
|
+
*
|
|
35
|
+
* The shared ModelRuntime is constructed offline (see authService.ts), so its
|
|
36
|
+
* own refreshes never touch the network and stay fast on request paths. This
|
|
37
|
+
* refresher is the single place that deliberately performs network refreshes —
|
|
38
|
+
* bounded by an abort timeout, serialized through one in-flight run, stopped by
|
|
39
|
+
* `dispose()` even mid-flight, and off any request path. `requestRefresh()`
|
|
40
|
+
* additionally asks for a prompt forced refresh after events that change what
|
|
41
|
+
* should be listed, such as provider logins, where the cached catalog is known
|
|
42
|
+
* to be wrong.
|
|
43
|
+
*
|
|
44
|
+
* When the operator asked for offline behavior (`PI_OFFLINE` / `PI_WEB_OFFLINE`),
|
|
45
|
+
* the refresher performs no network I/O at all and the cached catalogs in
|
|
46
|
+
* models-store.json are used as they are.
|
|
47
|
+
*/
|
|
48
|
+
export class ModelCatalogRefresher {
|
|
49
|
+
constructor(options) {
|
|
50
|
+
this.started = false;
|
|
51
|
+
this.disposed = false;
|
|
52
|
+
/**
|
|
53
|
+
* Aborted by `dispose()` so a refresh already in flight stops with the
|
|
54
|
+
* refresher instead of holding its fetch open for the rest of the timeout
|
|
55
|
+
* budget, which would delay daemon shutdown.
|
|
56
|
+
*/
|
|
57
|
+
this.lifetime = new AbortController();
|
|
58
|
+
this.runtime = options.runtime;
|
|
59
|
+
this.logger = options.logger ?? noopLogger;
|
|
60
|
+
this.offline = options.offline ?? false;
|
|
61
|
+
this.intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
62
|
+
this.initialDelayMs = options.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;
|
|
63
|
+
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
64
|
+
this.retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
65
|
+
}
|
|
66
|
+
/** Idempotent: a second call keeps the timers the first call installed. */
|
|
67
|
+
start() {
|
|
68
|
+
if (this.disposed || this.started)
|
|
69
|
+
return;
|
|
70
|
+
this.started = true;
|
|
71
|
+
if (this.offline) {
|
|
72
|
+
this.logger.info({}, "offline mode is enabled; skipping background model catalog refreshes");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
this.initialTimer = setTimeout(() => { this.queueRefresh("scheduled"); }, this.initialDelayMs);
|
|
76
|
+
this.initialTimer.unref();
|
|
77
|
+
this.intervalTimer = setInterval(() => { this.queueRefresh("scheduled"); }, this.intervalMs);
|
|
78
|
+
this.intervalTimer.unref();
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Ask for an immediate refresh that bypasses pi's freshness gate, for callers
|
|
82
|
+
* that know the cached catalog is stale (auth changes). Coalesces with
|
|
83
|
+
* concurrent and overlapping requests, and is a no-op in offline mode so auth
|
|
84
|
+
* changes never trigger network I/O either.
|
|
85
|
+
*/
|
|
86
|
+
requestRefresh() {
|
|
87
|
+
this.queueRefresh("forced");
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Whether a network catalog refresh is running right now. Read-only: callers
|
|
91
|
+
* that report what a slow operation is concurrent with need this fact, and
|
|
92
|
+
* must not be able to change the refresh schedule by asking for it.
|
|
93
|
+
*/
|
|
94
|
+
isRefreshInFlight() {
|
|
95
|
+
return this.inflight !== undefined;
|
|
96
|
+
}
|
|
97
|
+
/** Terminal: stops the schedule, drops any queued follow-up, and aborts an in-flight run. */
|
|
98
|
+
dispose() {
|
|
99
|
+
this.disposed = true;
|
|
100
|
+
if (this.initialTimer !== undefined)
|
|
101
|
+
clearTimeout(this.initialTimer);
|
|
102
|
+
if (this.intervalTimer !== undefined)
|
|
103
|
+
clearInterval(this.intervalTimer);
|
|
104
|
+
this.clearRetryTimer();
|
|
105
|
+
this.lifetime.abort();
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Single entry point for every refresh trigger. Only one run is ever in
|
|
109
|
+
* flight; overlapping requests collapse into one follow-up that keeps the
|
|
110
|
+
* strongest mode asked for, so a forced request is never downgraded.
|
|
111
|
+
*/
|
|
112
|
+
queueRefresh(mode, isRetry = false) {
|
|
113
|
+
if (this.disposed || this.offline)
|
|
114
|
+
return;
|
|
115
|
+
// Any fresh trigger supersedes a pending retry, which keeps retries from
|
|
116
|
+
// stacking up behind normal activity.
|
|
117
|
+
if (!isRetry)
|
|
118
|
+
this.clearRetryTimer();
|
|
119
|
+
if (this.inflight !== undefined) {
|
|
120
|
+
this.queuedMode = strongestMode(this.queuedMode, mode);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
// runCycle() reports every failure through the logger and never rejects.
|
|
124
|
+
this.inflight = this.runCycle(mode, isRetry);
|
|
125
|
+
}
|
|
126
|
+
/** One run plus the follow-up it leads to: a queued request, a single retry, or nothing. */
|
|
127
|
+
async runCycle(mode, isRetry) {
|
|
128
|
+
const outcome = await this.run(mode);
|
|
129
|
+
this.inflight = undefined;
|
|
130
|
+
const queued = this.queuedMode;
|
|
131
|
+
this.queuedMode = undefined;
|
|
132
|
+
if (queued !== undefined) {
|
|
133
|
+
// A request that arrived during the run replaces any retry this run would
|
|
134
|
+
// have earned; it runs now instead.
|
|
135
|
+
this.queueRefresh(queued);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// Only a first attempt earns a retry, so a failing provider can never turn
|
|
139
|
+
// into a retry loop.
|
|
140
|
+
if (outcome === "incomplete" && !isRetry)
|
|
141
|
+
this.scheduleRetry(mode);
|
|
142
|
+
}
|
|
143
|
+
scheduleRetry(mode) {
|
|
144
|
+
if (this.disposed)
|
|
145
|
+
return;
|
|
146
|
+
this.logger.info({ retryDelayMs: this.retryDelayMs, mode }, "scheduling one model catalog refresh retry");
|
|
147
|
+
this.retryTimer = setTimeout(() => {
|
|
148
|
+
this.retryTimer = undefined;
|
|
149
|
+
this.queueRefresh(mode, true);
|
|
150
|
+
}, this.retryDelayMs);
|
|
151
|
+
this.retryTimer.unref();
|
|
152
|
+
}
|
|
153
|
+
clearRetryTimer() {
|
|
154
|
+
if (this.retryTimer === undefined)
|
|
155
|
+
return;
|
|
156
|
+
clearTimeout(this.retryTimer);
|
|
157
|
+
this.retryTimer = undefined;
|
|
158
|
+
}
|
|
159
|
+
async run(mode) {
|
|
160
|
+
try {
|
|
161
|
+
const result = await this.runtime.refresh({
|
|
162
|
+
allowNetwork: true,
|
|
163
|
+
force: mode === "forced",
|
|
164
|
+
signal: AbortSignal.any([this.lifetime.signal, AbortSignal.timeout(this.timeoutMs)]),
|
|
165
|
+
});
|
|
166
|
+
if (result.aborted && this.lifetime.signal.aborted) {
|
|
167
|
+
this.logStoppedByDispose();
|
|
168
|
+
return "incomplete";
|
|
169
|
+
}
|
|
170
|
+
if (result.aborted) {
|
|
171
|
+
this.logger.warn({ timeoutMs: this.timeoutMs }, "model catalog refresh timed out; keeping cached catalogs");
|
|
172
|
+
}
|
|
173
|
+
if (result.errors.size > 0) {
|
|
174
|
+
const providers = [...result.errors.entries()].map(([providerId, error]) => `${providerId}: ${error.message}`);
|
|
175
|
+
this.logger.warn({ providers }, "model catalog refresh failed for some providers; keeping cached catalogs");
|
|
176
|
+
}
|
|
177
|
+
return result.aborted || result.errors.size > 0 ? "incomplete" : "complete";
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
if (this.lifetime.signal.aborted)
|
|
181
|
+
this.logStoppedByDispose();
|
|
182
|
+
else
|
|
183
|
+
this.logger.error({ err: error }, "model catalog refresh failed; keeping cached catalogs");
|
|
184
|
+
return "incomplete";
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/** A shutdown abort is expected, so it must not be reported as a timeout or a fault. */
|
|
188
|
+
logStoppedByDispose() {
|
|
189
|
+
this.logger.info({}, "model catalog refresh aborted by dispose; keeping cached catalogs");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=modelCatalogRefresher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modelCatalogRefresher.js","sourceRoot":"","sources":["../../../src/server/sessions/modelCatalogRefresher.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,mBAAmB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAC3C,mFAAmF;AACnF,MAAM,wBAAwB,GAAG,MAAM,CAAC;AACxC;;;;GAIG;AACH,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAClC;;;GAGG;AACH,MAAM,sBAAsB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAQ7C,2FAA2F;AAC3F,MAAM,aAAa,GAAG,CAAC,IAA6B,EAAE,KAAkB,EAAe,EAAE,CACvF,IAAI,KAAK,QAAQ,IAAI,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC;AA2BnE,MAAM,UAAU,GAAgC;IAC9C,IAAI,KAAiB,CAAC;IACtB,IAAI,KAAiB,CAAC;IACtB,KAAK,KAAiB,CAAC;CACxB,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,MAAM,OAAO,qBAAqB;IAsBhC,YAAY,OAAqC;QATzC,YAAO,GAAG,KAAK,CAAC;QAChB,aAAQ,GAAG,KAAK,CAAC;QACzB;;;;WAIG;QACc,aAAQ,GAAG,IAAI,eAAe,EAAE,CAAC;QAGhD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,UAAU,CAAC;QAC3C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,KAAK,CAAC;QACxC,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,mBAAmB,CAAC;QAC5D,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,wBAAwB,CAAC;QACzE,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,kBAAkB,CAAC;QACzD,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,sBAAsB,CAAC;IACrE,CAAC;IAED,2EAA2E;IAC3E,KAAK;QACH,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1C,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,sEAAsE,CAAC,CAAC;YAC7F,OAAO;QACT,CAAC;QACD,IAAI,CAAC,YAAY,GAAG,UAAU,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;QAC/F,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;QAC1B,IAAI,CAAC,aAAa,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAC7F,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC;IAC7B,CAAC;IAED;;;;;OAKG;IACH,cAAc;QACZ,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IAC9B,CAAC;IAED;;;;OAIG;IACH,iBAAiB;QACf,OAAO,IAAI,CAAC,QAAQ,KAAK,SAAS,CAAC;IACrC,CAAC;IAED,6FAA6F;IAC7F,OAAO;QACL,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,IAAI,CAAC,YAAY,KAAK,SAAS;YAAE,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACrE,IAAI,IAAI,CAAC,aAAa,KAAK,SAAS;YAAE,aAAa,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACxE,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAED;;;;OAIG;IACK,YAAY,CAAC,IAAiB,EAAE,OAAO,GAAG,KAAK;QACrD,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1C,yEAAyE;QACzE,sCAAsC;QACtC,IAAI,CAAC,OAAO;YAAE,IAAI,CAAC,eAAe,EAAE,CAAC;QACrC,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YAChC,IAAI,CAAC,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;YACvD,OAAO;QACT,CAAC;QACD,yEAAyE;QACzE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC/C,CAAC;IAED,4FAA4F;IACpF,KAAK,CAAC,QAAQ,CAAC,IAAiB,EAAE,OAAgB;QACxD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;QAC1B,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC;QAC/B,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC5B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,0EAA0E;YAC1E,oCAAoC;YACpC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;YAC1B,OAAO;QACT,CAAC;QACD,2EAA2E;QAC3E,qBAAqB;QACrB,IAAI,OAAO,KAAK,YAAY,IAAI,CAAC,OAAO;YAAE,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;IACrE,CAAC;IAEO,aAAa,CAAC,IAAiB;QACrC,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC1B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,IAAI,EAAE,EAAE,4CAA4C,CAAC,CAAC;QAC1G,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE;YAChC,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;YAC5B,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAChC,CAAC,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QACtB,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;IAC1B,CAAC;IAEO,eAAe;QACrB,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS;YAAE,OAAO;QAC1C,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC9B,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;IAC9B,CAAC;IAEO,KAAK,CAAC,GAAG,CAAC,IAAiB;QACjC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC;gBACxC,YAAY,EAAE,IAAI;gBAClB,KAAK,EAAE,IAAI,KAAK,QAAQ;gBACxB,MAAM,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;aACrF,CAAC,CAAC;YACH,IAAI,MAAM,CAAC,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnD,IAAI,CAAC,mBAAmB,EAAE,CAAC;gBAC3B,OAAO,YAAY,CAAC;YACtB,CAAC;YACD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE,0DAA0D,CAAC,CAAC;YAC9G,CAAC;YACD,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;gBAC3B,MAAM,SAAS,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,UAAU,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;gBAC/G,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,EAAE,0EAA0E,CAAC,CAAC;YAC9G,CAAC;YACD,OAAO,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC;QAC9E,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO;gBAAE,IAAI,CAAC,mBAAmB,EAAE,CAAC;;gBACxD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,uDAAuD,CAAC,CAAC;YAChG,OAAO,YAAY,CAAC;QACtB,CAAC;IACH,CAAC;IAED,wFAAwF;IAChF,mBAAmB;QACzB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,mEAAmE,CAAC,CAAC;IAC5F,CAAC;CACF"}
|