@mrclrchtr/supi-context 4.7.0 → 4.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/node_modules/@mrclrchtr/supi-core/README.md +29 -35
- package/node_modules/@mrclrchtr/supi-core/package.json +4 -7
- package/node_modules/@mrclrchtr/supi-core/src/api.ts +4 -6
- package/node_modules/@mrclrchtr/supi-core/src/config/config.ts +0 -20
- package/node_modules/@mrclrchtr/supi-core/src/config/prompt-surface.ts +7 -1
- package/node_modules/@mrclrchtr/supi-core/src/config.ts +0 -1
- package/node_modules/@mrclrchtr/supi-core/src/context.ts +1 -9
- package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +8 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug-timing.ts +107 -0
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +4 -6
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +54 -28
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +91 -125
- package/node_modules/@mrclrchtr/supi-core/src/settings.ts +10 -7
- package/package.json +2 -4
- package/src/settings-registration.ts +19 -16
- package/node_modules/@mrclrchtr/supi-core/src/context/context-messages.ts +0 -119
- package/node_modules/@mrclrchtr/supi-core/src/progress-widget.ts +0 -189
- package/node_modules/@mrclrchtr/supi-core/src/settings/scoped-settings-list.ts +0 -373
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +0 -102
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-command.ts +0 -15
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-submenus.ts +0 -141
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +0 -118
- package/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +0 -3
- package/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +0 -192
- package/src/api.ts +0 -1
- package/src/index.ts +0 -1
|
@@ -1,26 +1,19 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Fixed SuPi-config adapter for the canonical settings module interface.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// scope inheritance, source-state resolution, value rendering, persistence,
|
|
6
|
-
// and Inherit/Reset-to-default actions.
|
|
3
|
+
// Declarative field descriptors let the adapter own scope inheritance,
|
|
4
|
+
// source-state resolution, value rendering, persistence, and Unset actions.
|
|
7
5
|
//
|
|
8
6
|
// Custom fields remain for nested or unusual config; they report the same
|
|
9
7
|
// source state as declarative flat fields.
|
|
10
8
|
|
|
11
|
-
import type {
|
|
9
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
12
10
|
import type { Component } from "@earendil-works/pi-tui";
|
|
13
11
|
import {
|
|
14
12
|
loadSupiConfigSectionForScope,
|
|
15
13
|
removeSupiConfigKey,
|
|
16
14
|
writeSupiConfig,
|
|
17
15
|
} from "../config/config.ts";
|
|
18
|
-
import {
|
|
19
|
-
isSettingsContributionCollector,
|
|
20
|
-
type SettingsScope,
|
|
21
|
-
type SettingsSection,
|
|
22
|
-
SUPI_SETTINGS_COLLECT_EVENT,
|
|
23
|
-
} from "./settings-registry.ts";
|
|
16
|
+
import type { SettingsApplyResult, SettingsModule, SettingsScope } from "./settings-registry.ts";
|
|
24
17
|
|
|
25
18
|
// ── Types ──────────────────────────────────────────────────────────────────
|
|
26
19
|
|
|
@@ -52,10 +45,7 @@ export interface ConfigHelpers {
|
|
|
52
45
|
// ── Field actions ─────────────────────────────────────────────────────────
|
|
53
46
|
|
|
54
47
|
/** A user-initiated action on a settings row. */
|
|
55
|
-
export type
|
|
56
|
-
| { kind: "set"; value: string }
|
|
57
|
-
| { kind: "inherit" }
|
|
58
|
-
| { kind: "resetToDefault" };
|
|
48
|
+
export type SettingsAction = { kind: "set"; value: string } | { kind: "unset" };
|
|
59
49
|
|
|
60
50
|
// ── Field descriptors ─────────────────────────────────────────────────────
|
|
61
51
|
|
|
@@ -153,15 +143,15 @@ export interface CustomField extends BaseField {
|
|
|
153
143
|
ctx?: ExtensionContext,
|
|
154
144
|
) => Component;
|
|
155
145
|
/**
|
|
156
|
-
* Persist handler called on set
|
|
146
|
+
* Persist handler called on set or unset actions.
|
|
157
147
|
* Required for custom fields so they can write their nested config.
|
|
158
148
|
*/
|
|
159
149
|
persist: (
|
|
160
150
|
scope: SettingsScope,
|
|
161
151
|
cwd: string,
|
|
162
|
-
action:
|
|
152
|
+
action: SettingsAction,
|
|
163
153
|
helpers: ConfigHelpers,
|
|
164
|
-
) => void
|
|
154
|
+
) => void | Promise<void>;
|
|
165
155
|
}
|
|
166
156
|
|
|
167
157
|
/** Union of all supported field kinds. */
|
|
@@ -176,8 +166,8 @@ export type SettingsField =
|
|
|
176
166
|
|
|
177
167
|
// ── Contribution options ──────────────────────────────────────────────────
|
|
178
168
|
|
|
179
|
-
/** Options for
|
|
180
|
-
export interface
|
|
169
|
+
/** Options for the fixed SuPi-config settings adapter. */
|
|
170
|
+
export interface ConfigSettingsOptions {
|
|
181
171
|
/** Stable contribution identifier — e.g. "lsp", "claude-md". */
|
|
182
172
|
id: string;
|
|
183
173
|
/** Human-readable label shown in the UI. */
|
|
@@ -194,7 +184,7 @@ export interface DeclarativeSettingsOptions {
|
|
|
194
184
|
homeDir?: string;
|
|
195
185
|
}
|
|
196
186
|
|
|
197
|
-
// ──
|
|
187
|
+
// ── Source-aware row interface ───────────────────────────────────────────
|
|
198
188
|
|
|
199
189
|
/** Resolved value for one field in one scope. */
|
|
200
190
|
export interface ScopedFieldValue {
|
|
@@ -305,14 +295,14 @@ function createConfigHelpers(
|
|
|
305
295
|
};
|
|
306
296
|
}
|
|
307
297
|
|
|
308
|
-
// ──
|
|
298
|
+
// ── Fixed config adapter ─────────────────────────────────────────────────
|
|
309
299
|
|
|
310
300
|
interface NotifyAfterPersistInput {
|
|
311
|
-
options:
|
|
301
|
+
options: ConfigSettingsOptions;
|
|
312
302
|
field: SettingsField;
|
|
313
303
|
scope: SettingsScope;
|
|
314
304
|
cwd: string;
|
|
315
|
-
action:
|
|
305
|
+
action: SettingsAction;
|
|
316
306
|
storedValue: unknown;
|
|
317
307
|
ctx?: ExtensionContext;
|
|
318
308
|
}
|
|
@@ -354,86 +344,86 @@ function notifyAfterPersist(input: NotifyAfterPersistInput): void {
|
|
|
354
344
|
options.afterPersist(change);
|
|
355
345
|
}
|
|
356
346
|
|
|
357
|
-
function
|
|
347
|
+
function resolveConfigRows(
|
|
348
|
+
options: ConfigSettingsOptions,
|
|
349
|
+
scope: SettingsScope,
|
|
350
|
+
cwd: string,
|
|
351
|
+
ctx?: ExtensionContext,
|
|
352
|
+
): ScopedFieldValue[] {
|
|
358
353
|
const defaults = options.defaults as Record<string, unknown>;
|
|
354
|
+
const projectRaw = loadSupiConfigSectionForScope(options.section, cwd, {
|
|
355
|
+
scope: "project",
|
|
356
|
+
homeDir: options.homeDir,
|
|
357
|
+
});
|
|
358
|
+
const globalRaw = loadSupiConfigSectionForScope(options.section, cwd, {
|
|
359
|
+
scope: "global",
|
|
360
|
+
homeDir: options.homeDir,
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
return options.fields.map((field) => {
|
|
364
|
+
if (field.kind === "custom") {
|
|
365
|
+
const resolved = field.resolve(scope, cwd, ctx);
|
|
366
|
+
return {
|
|
367
|
+
field,
|
|
368
|
+
displayValue: resolved.displayValue
|
|
369
|
+
? sourceBadge(resolved.displayValue, resolved.source)
|
|
370
|
+
: "",
|
|
371
|
+
editValue: resolved.editValue ?? resolved.displayValue,
|
|
372
|
+
source: resolved.source,
|
|
373
|
+
inheritanceSource: resolved.inheritanceSource,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const { value, source } = resolveValue(field.key, defaults, projectRaw, globalRaw, scope);
|
|
378
|
+
const displayValue = formatValue(value, field);
|
|
379
|
+
const inheritanceSource =
|
|
380
|
+
scope === "project" && source === "project"
|
|
381
|
+
? globalRaw && field.key in globalRaw
|
|
382
|
+
? "global"
|
|
383
|
+
: "default"
|
|
384
|
+
: undefined;
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
field,
|
|
388
|
+
displayValue: sourceBadge(displayValue, source),
|
|
389
|
+
editValue: formatEditValue(value, field),
|
|
390
|
+
source,
|
|
391
|
+
inheritanceSource,
|
|
392
|
+
};
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
async function applyConfigAction(
|
|
397
|
+
options: ConfigSettingsOptions,
|
|
398
|
+
request: Parameters<SettingsModule["apply"]>[0],
|
|
399
|
+
): Promise<SettingsApplyResult> {
|
|
400
|
+
const { scope, cwd, fieldKey, action, ctx } = request;
|
|
401
|
+
const field = options.fields.find((candidate) => candidate.key === fieldKey);
|
|
402
|
+
if (!field) return {};
|
|
403
|
+
|
|
404
|
+
const helpers = createConfigHelpers(options.section, scope, cwd, options.homeDir);
|
|
405
|
+
let storedValue: unknown;
|
|
406
|
+
if (field.kind === "custom") {
|
|
407
|
+
await field.persist(scope, cwd, action, helpers);
|
|
408
|
+
storedValue = action.kind === "set" ? action.value : undefined;
|
|
409
|
+
} else if (action.kind === "set") {
|
|
410
|
+
storedValue = parseTypedValue(action.value, field);
|
|
411
|
+
helpers.set(field.key, storedValue);
|
|
412
|
+
} else {
|
|
413
|
+
helpers.unset(field.key);
|
|
414
|
+
}
|
|
415
|
+
notifyAfterPersist({ options, field, scope, cwd, action, storedValue, ctx });
|
|
416
|
+
return {};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/** Adapt one fixed SuPi config section to the canonical settings interface. */
|
|
420
|
+
export function defineConfigSettings(options: ConfigSettingsOptions): SettingsModule {
|
|
359
421
|
return {
|
|
360
422
|
id: options.id,
|
|
361
423
|
label: options.label,
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
scope: "project",
|
|
366
|
-
homeDir: options.homeDir,
|
|
367
|
-
});
|
|
368
|
-
const globalRaw = loadSupiConfigSectionForScope(options.section, cwd, {
|
|
369
|
-
scope: "global",
|
|
370
|
-
homeDir: options.homeDir,
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: natural discriminator on field kind and source
|
|
374
|
-
return options.fields.map((field) => {
|
|
375
|
-
if (field.kind === "custom") {
|
|
376
|
-
const resolved = field.resolve(scope, cwd, ctx);
|
|
377
|
-
return {
|
|
378
|
-
field,
|
|
379
|
-
displayValue: resolved.displayValue
|
|
380
|
-
? sourceBadge(resolved.displayValue, resolved.source)
|
|
381
|
-
: "",
|
|
382
|
-
editValue: resolved.editValue ?? resolved.displayValue,
|
|
383
|
-
source: resolved.source,
|
|
384
|
-
inheritanceSource: resolved.inheritanceSource,
|
|
385
|
-
};
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
const { value, source } = resolveValue(field.key, defaults, projectRaw, globalRaw, scope);
|
|
389
|
-
const displayValue = formatValue(value, field);
|
|
390
|
-
|
|
391
|
-
// Compute inheritanceSource for project-scope overrides
|
|
392
|
-
let inheritanceSource: "global" | "default" | undefined;
|
|
393
|
-
if (scope === "project" && source === "project") {
|
|
394
|
-
inheritanceSource = globalRaw && field.key in globalRaw ? "global" : "default";
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
return {
|
|
398
|
-
field,
|
|
399
|
-
displayValue: sourceBadge(displayValue, source),
|
|
400
|
-
editValue: formatEditValue(value, field),
|
|
401
|
-
source,
|
|
402
|
-
inheritanceSource,
|
|
403
|
-
};
|
|
404
|
-
});
|
|
405
|
-
},
|
|
406
|
-
// biome-ignore lint/complexity/useMaxParams: SettingsSection action handlers receive scope, cwd, field, action, and optional context
|
|
407
|
-
handleAction: (scope, cwd, fieldKey, action, ctx) => {
|
|
408
|
-
const field = options.fields.find((f) => f.key === fieldKey);
|
|
409
|
-
if (!field) return;
|
|
410
|
-
|
|
411
|
-
const section = options.section;
|
|
412
|
-
const helpers = createConfigHelpers(section, scope, cwd, options.homeDir);
|
|
413
|
-
let storedValue: unknown;
|
|
414
|
-
|
|
415
|
-
if (field.kind === "custom") {
|
|
416
|
-
field.persist(scope, cwd, action, helpers);
|
|
417
|
-
storedValue = action.kind === "set" ? action.value : undefined;
|
|
418
|
-
notifyAfterPersist({ options, field, scope, cwd, action, storedValue, ctx });
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
switch (action.kind) {
|
|
423
|
-
case "set": {
|
|
424
|
-
storedValue = parseTypedValue(action.value, field);
|
|
425
|
-
helpers.set(field.key, storedValue);
|
|
426
|
-
break;
|
|
427
|
-
}
|
|
428
|
-
case "inherit":
|
|
429
|
-
case "resetToDefault": {
|
|
430
|
-
helpers.unset(field.key);
|
|
431
|
-
break;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
notifyAfterPersist({ options, field, scope, cwd, action, storedValue, ctx });
|
|
436
|
-
},
|
|
424
|
+
read: ({ scope, cwd, ctx }) =>
|
|
425
|
+
Promise.resolve({ rows: resolveConfigRows(options, scope, cwd, ctx) }),
|
|
426
|
+
apply: (request) => applyConfigAction(options, request),
|
|
437
427
|
};
|
|
438
428
|
}
|
|
439
429
|
|
|
@@ -461,27 +451,3 @@ export function parseTypedValue(value: string, field: SettingsField): unknown {
|
|
|
461
451
|
return value;
|
|
462
452
|
}
|
|
463
453
|
}
|
|
464
|
-
|
|
465
|
-
// ── Registration ──────────────────────────────────────────────────────────
|
|
466
|
-
|
|
467
|
-
/**
|
|
468
|
-
* Register a declarative settings contribution for `/supi-settings`.
|
|
469
|
-
*
|
|
470
|
-
* Contributions are collected through PI's process-local event bus. Call this
|
|
471
|
-
* during the extension factory function, not in async session handlers.
|
|
472
|
-
*/
|
|
473
|
-
export function registerDeclarativeSettings(
|
|
474
|
-
pi: ExtensionAPI,
|
|
475
|
-
options: DeclarativeSettingsOptions,
|
|
476
|
-
): void {
|
|
477
|
-
const section = toDeclarativeSection(options);
|
|
478
|
-
const dispose = pi.events.on(SUPI_SETTINGS_COLLECT_EVENT, (collector) => {
|
|
479
|
-
if (isSettingsContributionCollector(collector)) {
|
|
480
|
-
collector.add(section);
|
|
481
|
-
}
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
pi.on("session_shutdown", () => {
|
|
485
|
-
dispose();
|
|
486
|
-
});
|
|
487
|
-
}
|
|
@@ -1,33 +1,36 @@
|
|
|
1
|
-
// supi-core settings domain —
|
|
2
|
-
|
|
3
|
-
export { registerSettingsCommand } from "./settings/settings-command.ts";
|
|
1
|
+
// supi-core settings domain — settings modules and fixed-config adapters.
|
|
4
2
|
export type {
|
|
3
|
+
SettingsActionRequest,
|
|
4
|
+
SettingsApplyResult,
|
|
5
5
|
SettingsCollectionDiagnostic,
|
|
6
6
|
SettingsCollectionResult,
|
|
7
|
+
SettingsContext,
|
|
7
8
|
SettingsContributionCollector,
|
|
9
|
+
SettingsModule,
|
|
8
10
|
SettingsScope,
|
|
9
|
-
|
|
11
|
+
SettingsSnapshot,
|
|
10
12
|
} from "./settings/settings-registry.ts";
|
|
11
13
|
export {
|
|
12
14
|
createSettingsContributionCollector,
|
|
13
15
|
isSettingsContributionCollector,
|
|
16
|
+
registerSettings,
|
|
14
17
|
SUPI_SETTINGS_COLLECT_EVENT,
|
|
15
18
|
} from "./settings/settings-registry.ts";
|
|
16
19
|
export type {
|
|
17
20
|
BoolField,
|
|
18
21
|
ConfigHelpers,
|
|
22
|
+
ConfigSettingsOptions,
|
|
19
23
|
CustomField,
|
|
20
|
-
DeclarativeSettingsOptions,
|
|
21
24
|
EnumField,
|
|
22
25
|
ModelPickerField,
|
|
23
26
|
ModelPickerStaticOption,
|
|
24
27
|
NumberField,
|
|
25
28
|
ScopedFieldValue,
|
|
29
|
+
SettingsAction,
|
|
26
30
|
SettingsField,
|
|
27
|
-
SettingsFieldAction,
|
|
28
31
|
SettingsPersistedChange,
|
|
29
32
|
StringField,
|
|
30
33
|
StringListField,
|
|
31
34
|
ValueSource,
|
|
32
35
|
} from "./settings/settings-schema.ts";
|
|
33
|
-
export {
|
|
36
|
+
export { defineConfigSettings } from "./settings/settings-schema.ts";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-context",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.9.0",
|
|
4
4
|
"description": "Live context pressure and token-use reports",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"typebox": "*",
|
|
35
|
-
"@mrclrchtr/supi-core": "4.
|
|
35
|
+
"@mrclrchtr/supi-core": "4.9.0"
|
|
36
36
|
},
|
|
37
37
|
"bundledDependencies": [
|
|
38
38
|
"@mrclrchtr/supi-core"
|
|
@@ -59,9 +59,7 @@
|
|
|
59
59
|
],
|
|
60
60
|
"image": "https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-context/assets/social-preview.png"
|
|
61
61
|
},
|
|
62
|
-
"main": "src/api.ts",
|
|
63
62
|
"exports": {
|
|
64
|
-
"./api": "./src/api.ts",
|
|
65
63
|
"./extension": "./src/extension.ts",
|
|
66
64
|
"./package.json": "./package.json"
|
|
67
65
|
}
|
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
// supi-context settings registration for the supi settings registry.
|
|
2
2
|
|
|
3
3
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import {
|
|
4
|
+
import { defineConfigSettings, registerSettings } from "@mrclrchtr/supi-core/settings";
|
|
5
5
|
import { CONTEXT_DEFAULTS } from "./config.ts";
|
|
6
6
|
|
|
7
7
|
/** Register supi-context settings with the supi settings registry. */
|
|
8
8
|
export function registerContextSettings(pi: ExtensionAPI, homeDir?: string): void {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
9
|
+
registerSettings(
|
|
10
|
+
pi,
|
|
11
|
+
defineConfigSettings({
|
|
12
|
+
id: "context",
|
|
13
|
+
label: "Context",
|
|
14
|
+
section: "context",
|
|
15
|
+
defaults: CONTEXT_DEFAULTS,
|
|
16
|
+
fields: [
|
|
17
|
+
{
|
|
18
|
+
kind: "boolean" as const,
|
|
19
|
+
key: "agentToolEnabled",
|
|
20
|
+
label: "Agent Tool",
|
|
21
|
+
description: "Enable supi_context agent tool for context usage queries",
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
...(homeDir ? { homeDir } : {}),
|
|
25
|
+
}),
|
|
26
|
+
);
|
|
24
27
|
}
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
// Shared context-message utilities for SuPi extensions.
|
|
2
|
-
//
|
|
3
|
-
// Provides a generic prune-and-reorder pattern for extensions that inject
|
|
4
|
-
// managed context messages (via `before_agent_start` with a `customType` and
|
|
5
|
-
// `contextToken`) and maintain them via the `context` event.
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Minimal message shape needed for context-message operations.
|
|
9
|
-
* Extensions cast their event.messages entries to this type.
|
|
10
|
-
*/
|
|
11
|
-
export type ContextMessageLike = {
|
|
12
|
-
role?: string;
|
|
13
|
-
customType?: string;
|
|
14
|
-
content?: unknown;
|
|
15
|
-
details?: unknown;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Extract the `contextToken` string from a message's `details` object.
|
|
20
|
-
* Returns `null` when the token is absent or not a string.
|
|
21
|
-
*/
|
|
22
|
-
export function getContextToken(details: unknown): string | null {
|
|
23
|
-
if (!details || typeof details !== "object") return null;
|
|
24
|
-
const token = (details as { contextToken?: unknown }).contextToken;
|
|
25
|
-
return typeof token === "string" ? token : null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Find the index of the last message with `role: "user"`.
|
|
30
|
-
* Returns `-1` when no user message exists.
|
|
31
|
-
*/
|
|
32
|
-
export function findLastUserMessageIndex<T extends ContextMessageLike>(messages: T[]): number {
|
|
33
|
-
for (let index = messages.length - 1; index >= 0; index--) {
|
|
34
|
-
if (messages[index]?.role === "user") return index;
|
|
35
|
-
}
|
|
36
|
-
return -1;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Filter stale context messages and reorder the active one before the last user message.
|
|
41
|
-
*
|
|
42
|
-
* - Removes all messages matching `customType` whose token differs from `activeToken`.
|
|
43
|
-
* - When `activeToken` is `null`, removes **all** messages of that `customType`.
|
|
44
|
-
* - If the active context message is after the last user message, moves it before.
|
|
45
|
-
*
|
|
46
|
-
* Returns the modified array, or the original reference when no changes were needed.
|
|
47
|
-
*/
|
|
48
|
-
export function pruneAndReorderContextMessages<T extends ContextMessageLike>(
|
|
49
|
-
messages: T[],
|
|
50
|
-
customType: string,
|
|
51
|
-
activeToken: string | null,
|
|
52
|
-
): T[] {
|
|
53
|
-
// Remove stale messages of the target customType
|
|
54
|
-
const filtered = messages.filter((message) => {
|
|
55
|
-
if (message.customType !== customType) return true;
|
|
56
|
-
if (!activeToken) return false;
|
|
57
|
-
return getContextToken(message.details) === activeToken;
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
if (!activeToken) return filtered;
|
|
61
|
-
|
|
62
|
-
// Find the active context message
|
|
63
|
-
const contextIndex = filtered.findIndex(
|
|
64
|
-
(message) =>
|
|
65
|
-
message.customType === customType && getContextToken(message.details) === activeToken,
|
|
66
|
-
);
|
|
67
|
-
if (contextIndex === -1) return filtered;
|
|
68
|
-
|
|
69
|
-
// Find the last user message
|
|
70
|
-
const userIndex = findLastUserMessageIndex(filtered);
|
|
71
|
-
if (userIndex === -1 || contextIndex < userIndex) return filtered;
|
|
72
|
-
|
|
73
|
-
// Move context message before last user message
|
|
74
|
-
const next = [...filtered];
|
|
75
|
-
const [contextMessage] = next.splice(contextIndex, 1);
|
|
76
|
-
if (!contextMessage) return filtered;
|
|
77
|
-
next.splice(userIndex, 0, contextMessage);
|
|
78
|
-
return next;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Restore the raw prompt content on a context message that was swapped for display text.
|
|
83
|
-
*
|
|
84
|
-
* Extensions using `registerMessageRenderer` store their LLM-facing content in
|
|
85
|
-
* `details.promptContent` and put a human-readable summary in `content`. This function
|
|
86
|
-
* reverses the swap so the model sees the original prompt content.
|
|
87
|
-
*
|
|
88
|
-
* Returns the original array reference when no change is needed.
|
|
89
|
-
*/
|
|
90
|
-
export function restorePromptContent<T extends ContextMessageLike>(
|
|
91
|
-
messages: T[],
|
|
92
|
-
customType: string,
|
|
93
|
-
activeToken: string | null,
|
|
94
|
-
): T[] {
|
|
95
|
-
if (!activeToken) return messages;
|
|
96
|
-
|
|
97
|
-
const index = messages.findIndex(
|
|
98
|
-
(message) =>
|
|
99
|
-
message.customType === customType && getContextToken(message.details) === activeToken,
|
|
100
|
-
);
|
|
101
|
-
if (index === -1) return messages;
|
|
102
|
-
|
|
103
|
-
const promptContent = getPromptContent(messages[index]?.details);
|
|
104
|
-
if (!promptContent || messages[index]?.content === promptContent) return messages;
|
|
105
|
-
|
|
106
|
-
const next = [...messages];
|
|
107
|
-
next[index] = { ...next[index], content: promptContent };
|
|
108
|
-
return next;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Extract the `promptContent` string from a message's `details` object.
|
|
113
|
-
* Returns `null` when absent or not a string.
|
|
114
|
-
*/
|
|
115
|
-
export function getPromptContent(details: unknown): string | null {
|
|
116
|
-
if (!details || typeof details !== "object") return null;
|
|
117
|
-
const promptContent = (details as { promptContent?: unknown }).promptContent;
|
|
118
|
-
return typeof promptContent === "string" ? promptContent : null;
|
|
119
|
-
}
|