@nijaru/pi-provider-compaction 0.0.8 → 0.0.9
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 +51 -15
- package/cached-summary.ts +230 -0
- package/dist/index.js +751 -190
- package/index.ts +192 -58
- package/package.json +6 -5
- package/protocol.ts +11 -1
- package/provider.ts +152 -37
package/provider.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import type { Provider, StreamOptions, Model, TranscriptContext } from "@earendil-works/pi-ai";
|
|
1
|
+
import type { AssistantMessage, AssistantMessageEventStream, CacheRetention, Provider, StreamOptions, Model, TranscriptContext } from "@earendil-works/pi-ai";
|
|
2
2
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
|
|
4
|
-
export type
|
|
4
|
+
export type PayloadObserver = ((payload: unknown) => unknown | Promise<unknown>) & {
|
|
5
|
+
complete?: (message: AssistantMessage) => void;
|
|
6
|
+
cacheRetention?: CacheRetention;
|
|
7
|
+
};
|
|
8
|
+
export type ObserveRequest = (model: Model<any>, context: TranscriptContext, options: StreamOptions) => PayloadObserver | undefined;
|
|
5
9
|
|
|
6
10
|
export interface InstalledObserver {
|
|
7
11
|
provider: string;
|
|
@@ -13,26 +17,101 @@ export interface InstalledObserver {
|
|
|
13
17
|
// streams: this observer delegates unchanged and can release only its own slot.
|
|
14
18
|
export const STREAM_OBSERVER = Symbol.for("@nijaru/pi-provider-compaction/stream-observer");
|
|
15
19
|
|
|
16
|
-
function
|
|
20
|
+
function observedStream<T extends StreamOptions>(model: Model<any>, context: TranscriptContext, options: T | undefined, observe: ObserveRequest, dispatch: (options: T | undefined) => AssistantMessageEventStream): AssistantMessageEventStream {
|
|
17
21
|
let handler: ReturnType<ObserveRequest>;
|
|
18
22
|
try { handler = observe(model, context, options ?? {}); }
|
|
19
|
-
catch { return options; } // An expired
|
|
20
|
-
if (!handler) return options;
|
|
21
|
-
|
|
23
|
+
catch { return dispatch(options); } // An expired optional observer must not veto dispatch.
|
|
24
|
+
if (!handler) return dispatch(options);
|
|
25
|
+
const stream = dispatch({
|
|
22
26
|
...options,
|
|
27
|
+
...(handler.cacheRetention === undefined ? {} : { cacheRetention: handler.cacheRetention }),
|
|
23
28
|
onPayload: async (payload: unknown, requestModel: Model<any>) => {
|
|
24
29
|
const replacement = await options?.onPayload?.(payload, requestModel);
|
|
25
30
|
const prepared = replacement === undefined ? payload : replacement;
|
|
26
31
|
// Observation failures must not suppress or alter the portable request.
|
|
27
32
|
try { return await handler(prepared); } catch { return prepared; }
|
|
28
33
|
},
|
|
29
|
-
} as T;
|
|
34
|
+
} as T);
|
|
35
|
+
// result() observes completion without consuming the public event iterator.
|
|
36
|
+
if (handler.complete) void stream.result().then(handler.complete).catch(() => {});
|
|
37
|
+
return stream;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** One dispatch layer, with session-local selection and no released lease history. */
|
|
41
|
+
function observerLeases(provider: string, owned: () => boolean, restore: () => void) {
|
|
42
|
+
const leases = new Map<symbol, { observe: ObserveRequest; current: () => boolean }>();
|
|
43
|
+
let closed = false;
|
|
44
|
+
const active = (token: symbol) => !closed && leases.get(token)?.current() === true && owned();
|
|
45
|
+
const release = () => {
|
|
46
|
+
if (closed) return;
|
|
47
|
+
closed = true;
|
|
48
|
+
leases.clear();
|
|
49
|
+
restore();
|
|
50
|
+
};
|
|
51
|
+
const observe: ObserveRequest = (...args) => {
|
|
52
|
+
const handlers: PayloadObserver[] = [];
|
|
53
|
+
for (const [token, lease] of leases) {
|
|
54
|
+
try {
|
|
55
|
+
if (!active(token)) continue;
|
|
56
|
+
const handler = lease.observe(...args);
|
|
57
|
+
if (!handler) continue;
|
|
58
|
+
handlers.push(Object.assign((payload: unknown) => active(token) ? handler(payload) : payload, {
|
|
59
|
+
cacheRetention: handler.cacheRetention,
|
|
60
|
+
complete: handler.complete ? (message: AssistantMessage) => { if (active(token)) handler.complete!(message); } : undefined,
|
|
61
|
+
}));
|
|
62
|
+
} catch { /* A failed optional observer must not disable the other sessions. */ }
|
|
63
|
+
}
|
|
64
|
+
if (!handlers.length) return;
|
|
65
|
+
return Object.assign(async (payload: unknown) => {
|
|
66
|
+
for (const handler of handlers) {
|
|
67
|
+
try {
|
|
68
|
+
const replacement = await handler(payload);
|
|
69
|
+
if (replacement !== undefined) payload = replacement;
|
|
70
|
+
} catch { /* Keep the last valid request on optional observation failure. */ }
|
|
71
|
+
}
|
|
72
|
+
return payload;
|
|
73
|
+
}, {
|
|
74
|
+
cacheRetention: [...handlers].reverse().find((handler) => handler.cacheRetention !== undefined)?.cacheRetention,
|
|
75
|
+
complete: handlers.some((handler) => handler.complete) ? (message: AssistantMessage) => {
|
|
76
|
+
for (const handler of handlers) {
|
|
77
|
+
try { handler.complete?.(message); } catch { /* Isolate optional completion observers too. */ }
|
|
78
|
+
}
|
|
79
|
+
} : undefined,
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
return {
|
|
83
|
+
observe, release, isOpen: () => !closed,
|
|
84
|
+
add(observe: ObserveRequest, current: () => boolean = () => true): InstalledObserver {
|
|
85
|
+
const token = Symbol();
|
|
86
|
+
leases.set(token, { observe, current });
|
|
87
|
+
return { provider, isInstalled: () => active(token), remove() {
|
|
88
|
+
leases.delete(token);
|
|
89
|
+
if (!leases.size) release();
|
|
90
|
+
} };
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
type ObserverLeases = ReturnType<typeof observerLeases>;
|
|
96
|
+
// Pi's uncached module loader can instantiate this extension separately for
|
|
97
|
+
// sessions sharing one model runtime. Weak ownership must span those instances.
|
|
98
|
+
const LEASE_TABLES = Symbol.for("@nijaru/pi-provider-compaction/observer-leases-v1");
|
|
99
|
+
interface LeaseTables {
|
|
100
|
+
effectiveLeases: WeakMap<Provider, { owned(): boolean; leases: ObserverLeases }>;
|
|
101
|
+
effectiveRestores: WeakMap<Function, { base: Provider; restore(): void }>;
|
|
102
|
+
nativeLeases: WeakMap<Provider, ObserverLeases>;
|
|
103
|
+
legacyLeases: WeakMap<Provider["streamSimple"], ObserverLeases>;
|
|
30
104
|
}
|
|
105
|
+
const shared = globalThis as typeof globalThis & { [LEASE_TABLES]?: LeaseTables };
|
|
106
|
+
const { effectiveLeases, effectiveRestores, nativeLeases, legacyLeases } = shared[LEASE_TABLES] ??= {
|
|
107
|
+
effectiveLeases: new WeakMap(), effectiveRestores: new WeakMap(),
|
|
108
|
+
nativeLeases: new WeakMap(), legacyLeases: new WeakMap(),
|
|
109
|
+
};
|
|
31
110
|
|
|
32
111
|
/** Preserve method receivers, getters, optional members and dynamic catalogs. */
|
|
33
112
|
export function decorateProvider(base: Provider, observe: ObserveRequest): Provider {
|
|
34
|
-
const stream: Provider["stream"] = (model, context, options) =>
|
|
35
|
-
const streamSimple: Provider["streamSimple"] = (model, context, options) =>
|
|
113
|
+
const stream: Provider["stream"] = (model, context, options) => observedStream(model, context, options, observe, (next) => base.stream(model, context, next));
|
|
114
|
+
const streamSimple: Provider["streamSimple"] = (model, context, options) => observedStream(model, context, options, observe, (next) => base.streamSimple(model, context, next));
|
|
36
115
|
const bound = new Map<PropertyKey, unknown>();
|
|
37
116
|
return new Proxy(base, {
|
|
38
117
|
get(target, key) {
|
|
@@ -46,6 +125,43 @@ export function decorateProvider(base: Provider, observe: ObserveRequest): Provi
|
|
|
46
125
|
});
|
|
47
126
|
}
|
|
48
127
|
|
|
128
|
+
/** Observe a builtin/composer in place, preserving its auth, catalogs and dispatch closure. */
|
|
129
|
+
export function leaseEffectiveProvider(base: Provider | undefined, current: () => Provider | undefined, observe: ObserveRequest): InstalledObserver | undefined {
|
|
130
|
+
if (!base) return;
|
|
131
|
+
const existing = effectiveLeases.get(base);
|
|
132
|
+
if (existing?.leases.isOpen() && existing.owned()) return existing.leases.add(observe, () => current() === base);
|
|
133
|
+
// External replacement invalidated the old pair. Restore its still-owned
|
|
134
|
+
// method before capturing delegates, rather than burying a released wrapper.
|
|
135
|
+
existing?.leases.release();
|
|
136
|
+
// A third party may have restored saved method values after their release.
|
|
137
|
+
// They remain inert, but must not become delegates of another observation layer.
|
|
138
|
+
const keys = ["stream", "streamSimple"] as const;
|
|
139
|
+
for (const key of keys) {
|
|
140
|
+
const saved = effectiveRestores.get(base[key]);
|
|
141
|
+
if (saved?.base === base) saved.restore();
|
|
142
|
+
}
|
|
143
|
+
const descriptors = keys.map((key) => Object.getOwnPropertyDescriptor(base, key));
|
|
144
|
+
if (descriptors.some((descriptor) => descriptor && (!("value" in descriptor) || !descriptor.configurable && !descriptor.writable)) || !Object.isExtensible(base) && descriptors.some((descriptor) => !descriptor)) return;
|
|
145
|
+
const originalStream = base.stream, originalSimple = base.streamSimple;
|
|
146
|
+
const owned = () => base.stream === stream && base.streamSimple === streamSimple;
|
|
147
|
+
const restore = () => {
|
|
148
|
+
for (const [index, key] of keys.entries()) if (base[key] === (key === "stream" ? stream : streamSimple)) {
|
|
149
|
+
const descriptor = descriptors[index];
|
|
150
|
+
if (descriptor) Object.defineProperty(base, key, descriptor);
|
|
151
|
+
else Reflect.deleteProperty(base, key);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const leases = observerLeases(base.id, owned, restore);
|
|
155
|
+
const stream: Provider["stream"] = (model, context, options) => observedStream(model, context, options, leases.observe, (next) => originalStream.call(base, model, context, next));
|
|
156
|
+
const streamSimple: Provider["streamSimple"] = (model, context, options) => observedStream(model, context, options, leases.observe, (next) => originalSimple.call(base, model, context, next));
|
|
157
|
+
try {
|
|
158
|
+
for (const [index, key] of keys.entries()) Object.defineProperty(base, key, { ...descriptors[index], value: key === "stream" ? stream : streamSimple, ...(descriptors[index] ? {} : { configurable: true, writable: true, enumerable: true }) });
|
|
159
|
+
} catch { leases.release(); return; }
|
|
160
|
+
for (const method of [stream, streamSimple]) effectiveRestores.set(method, { base, restore });
|
|
161
|
+
effectiveLeases.set(base, { owned, leases });
|
|
162
|
+
return leases.add(observe, () => current() === base);
|
|
163
|
+
}
|
|
164
|
+
|
|
49
165
|
/**
|
|
50
166
|
* Pi 0.87 has replacement registration, not middleware. Native BASE providers can
|
|
51
167
|
* be decorated directly. Legacy stream overlays retain their configuration and
|
|
@@ -64,45 +180,44 @@ export function installObserver(pi: ExtensionAPI, ctx: ExtensionContext, observe
|
|
|
64
180
|
// wrapper; claiming an observer there would report native compaction as
|
|
65
181
|
// installed while never capturing anything.
|
|
66
182
|
if (typeof native.getModels !== "function" || !native.getModels().some((entry) => entry.api === model.api)) return;
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
183
|
+
const existing = nativeLeases.get(native);
|
|
184
|
+
if (existing) return existing.add(observe);
|
|
185
|
+
const leases = observerLeases(id, () => registry.getRegisteredNativeProvider(id) === wrapper, () => {
|
|
186
|
+
// Keep cleanup usable after bare SDK disposal invalidates the extension API.
|
|
187
|
+
if (registry.getRegisteredNativeProvider(id) === wrapper) registry.registerProvider(native);
|
|
188
|
+
nativeLeases.delete(wrapper);
|
|
189
|
+
});
|
|
190
|
+
const wrapper = decorateProvider(native, leases.observe);
|
|
191
|
+
nativeLeases.set(wrapper, leases);
|
|
74
192
|
pi.registerProvider(wrapper);
|
|
75
|
-
return
|
|
76
|
-
provider: id, isInstalled,
|
|
77
|
-
remove() {
|
|
78
|
-
// Keep cleanup usable after bare SDK disposal invalidates the extension API.
|
|
79
|
-
if (isInstalled()) registry.registerProvider(native);
|
|
80
|
-
},
|
|
81
|
-
};
|
|
193
|
+
return leases.add(observe);
|
|
82
194
|
}
|
|
83
195
|
const original = registry.getRegisteredProviderConfig(id);
|
|
84
196
|
// Refresh callbacks own mutable catalog state inside the composer. Replacing
|
|
85
197
|
// that composer cannot promise preservation, so leave it alone.
|
|
86
|
-
if (!original?.streamSimple || original.refreshModels || original.oauth?.modifyModels)
|
|
87
|
-
|
|
198
|
+
if (!original?.streamSimple || original.refreshModels || original.oauth?.modifyModels || original.api !== model.api) {
|
|
199
|
+
// Lease the effective object's methods, NOT its registry slot. Re-registering
|
|
200
|
+
// a composed provider freezes catalogs and loses overlay/header semantics.
|
|
201
|
+
return leaseEffectiveProvider(registry.getProvider(id), () => registry.getProvider(id), observe);
|
|
202
|
+
}
|
|
88
203
|
// An injected legacy streamSimple overlay would also intercept full stream()
|
|
89
204
|
// calls and change reasoning semantics. Only decorate an existing stream owner.
|
|
205
|
+
const existing = legacyLeases.get(original.streamSimple);
|
|
206
|
+
if (existing) return existing.add(observe);
|
|
90
207
|
const delegate = original.streamSimple.bind(original);
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return handler ? (payload) => isInstalled() ? handler(payload) : payload : undefined;
|
|
96
|
-
};
|
|
97
|
-
const wrapper: Provider["streamSimple"] = (requestModel, context, options) => delegate(requestModel, context, optionsWithObserver(requestModel, context, options, guarded));
|
|
98
|
-
const remove = () => {
|
|
99
|
-
if (!isInstalled()) return;
|
|
208
|
+
const owned = () => registry.getRegisteredProviderConfig(id)?.streamSimple === wrapper && !registry.getRegisteredNativeProvider(id);
|
|
209
|
+
const leases = observerLeases(id, owned, () => {
|
|
210
|
+
legacyLeases.delete(wrapper);
|
|
211
|
+
if (!owned()) return;
|
|
100
212
|
// registerProvider merges undefined properties: remove our layer before restore.
|
|
101
213
|
const restored = { ...registry.getRegisteredProviderConfig(id)!, streamSimple: original.streamSimple };
|
|
102
214
|
registry.unregisterProvider(id);
|
|
103
215
|
registry.registerProvider(id, restored);
|
|
104
|
-
};
|
|
105
|
-
|
|
216
|
+
});
|
|
217
|
+
const wrapper: Provider["streamSimple"] = (requestModel, context, options) => observedStream(requestModel, context, options, leases.observe, (next) => delegate(requestModel, context, next));
|
|
218
|
+
// /fast owns the delegate: its cleanup must release all observing sessions.
|
|
219
|
+
Object.defineProperty(wrapper, STREAM_OBSERVER, { value: { delegate: original.streamSimple, release: leases.release } });
|
|
220
|
+
legacyLeases.set(wrapper, leases);
|
|
106
221
|
pi.registerProvider(id, { ...original, streamSimple: wrapper });
|
|
107
|
-
return
|
|
222
|
+
return leases.add(observe);
|
|
108
223
|
}
|