@nevescloud/pip 3.8.3 → 3.9.1
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 +1 -1
- package/package.json +1 -1
- package/providers/local.esm.js +20 -1
- package/runtime.esm.js +68 -2
package/README.md
CHANGED
|
@@ -210,4 +210,4 @@ For a turn loop, tool dispatch, history, and an Anthropic provider, pair pip-cor
|
|
|
210
210
|
|
|
211
211
|
## Demo
|
|
212
212
|
|
|
213
|
-
`docs/index.html` is a standalone demo wiring
|
|
213
|
+
`docs/index.html` is a standalone demo wiring three stub providers (`echo`, `reverse`, `danger`) through the runtime. Open it locally to play with the slash autocomplete, `/model` switching, and the chat shell without needing an API key. The `danger` stub fires a `delete_thing` tool_use that's gated by a `preToolUse` hook (Run/Cancel via `askInChat`) — a working example of the runtime's hook events.
|
package/package.json
CHANGED
package/providers/local.esm.js
CHANGED
|
@@ -257,9 +257,21 @@ export function createTransformersRenderer() {
|
|
|
257
257
|
return splitThinking(buffer).answer || buffer.trim();
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
+
// Background warm — starts the transformers.js import + tokenizer +
|
|
261
|
+
// model download without running inference. Hosts call this from
|
|
262
|
+
// requestIdleCallback after their page boots so the first user prompt
|
|
263
|
+
// doesn't pay the cold-start cost. Safe to call repeatedly; ensureLoaded
|
|
264
|
+
// is internally idempotent (returns the in-flight loadingPromise).
|
|
265
|
+
async function warm(turnEl) {
|
|
266
|
+
if (!config?.id) return;
|
|
267
|
+
try { await ensureLoaded(turnEl); }
|
|
268
|
+
catch { /* swallow — first real generate() will re-throw with the same error */ }
|
|
269
|
+
}
|
|
270
|
+
|
|
260
271
|
return {
|
|
261
272
|
setModel,
|
|
262
273
|
generate,
|
|
274
|
+
warm,
|
|
263
275
|
get currentModelId() { return config?.id || null; },
|
|
264
276
|
};
|
|
265
277
|
}
|
|
@@ -295,7 +307,7 @@ export function local({
|
|
|
295
307
|
const renderer = createTransformersRenderer();
|
|
296
308
|
if (model) renderer.setModel({ id: model, dtype, maxTokens, genParams, chatTemplate });
|
|
297
309
|
|
|
298
|
-
|
|
310
|
+
const provider = ({ messages, signal, system, tools, turnEl, setReplyText }) => (async function* () {
|
|
299
311
|
const effectiveSystem = system || systemPrompt || '';
|
|
300
312
|
const augmentedSystem = buildToolSystemPrompt(effectiveSystem, tools);
|
|
301
313
|
|
|
@@ -347,6 +359,13 @@ export function local({
|
|
|
347
359
|
if (error) throw error;
|
|
348
360
|
yield { type: 'turn_end', stopReason: sawToolUse ? 'tool_use' : 'end_turn' };
|
|
349
361
|
})();
|
|
362
|
+
|
|
363
|
+
// Surface the renderer's background warm() on the provider so hosts
|
|
364
|
+
// can trigger transformers.js import + weight download in idle time
|
|
365
|
+
// ahead of the first user message. Idempotent; no-op until setModel
|
|
366
|
+
// has run (covered above when `model` is passed).
|
|
367
|
+
provider.warm = (turnEl) => renderer.warm(turnEl);
|
|
368
|
+
return provider;
|
|
350
369
|
}
|
|
351
370
|
|
|
352
371
|
export { splitThinking };
|
package/runtime.esm.js
CHANGED
|
@@ -51,6 +51,24 @@ export function createRuntime({
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
// Hook-style emit: awaits each handler in registration order and returns
|
|
55
|
+
// the first non-undefined return value. Lets host code on `preTurn` and
|
|
56
|
+
// `preToolUse` block, defer (e.g. await an askInChat approval), or mutate
|
|
57
|
+
// the action without a separate `hook(...)` channel.
|
|
58
|
+
async function emitAsync(name, payload) {
|
|
59
|
+
const set = listeners.get(name);
|
|
60
|
+
if (!set) return undefined;
|
|
61
|
+
for (const fn of set) {
|
|
62
|
+
try {
|
|
63
|
+
const ret = await fn(payload);
|
|
64
|
+
if (ret !== undefined) return ret;
|
|
65
|
+
} catch (e) {
|
|
66
|
+
console.warn(`[pip-runtime] handler for "${name}" threw`, e);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
54
72
|
function on(name, handler) {
|
|
55
73
|
if (!listeners.has(name)) listeners.set(name, new Set());
|
|
56
74
|
listeners.get(name).add(handler);
|
|
@@ -111,6 +129,26 @@ export function createRuntime({
|
|
|
111
129
|
abortCtrl = new AbortController();
|
|
112
130
|
emit('turnStart', { turnEl, userText });
|
|
113
131
|
|
|
132
|
+
// `added` snapshot: anything pushed after this index was produced by
|
|
133
|
+
// *this* turn (assistant messages + tool roundtrips). The user message
|
|
134
|
+
// was pushed in onSubmit before we entered, so it's excluded — hosts
|
|
135
|
+
// already have it in payload.userText.
|
|
136
|
+
const startIdx = messages.length;
|
|
137
|
+
|
|
138
|
+
// preTurn fires once per user message, after history is sealed and
|
|
139
|
+
// before any provider call. Handler can return { system } to override
|
|
140
|
+
// the system prompt for this turn loop — the use case is per-turn RAG
|
|
141
|
+
// injection that depends on userText. Returning undefined falls through
|
|
142
|
+
// to the configured systemFn().
|
|
143
|
+
const baseSystem = systemFn();
|
|
144
|
+
const preTurn = await emitAsync('preTurn', {
|
|
145
|
+
turnEl,
|
|
146
|
+
userText,
|
|
147
|
+
messages: messages.slice(),
|
|
148
|
+
system: baseSystem,
|
|
149
|
+
});
|
|
150
|
+
const turnSystem = preTurn?.system !== undefined ? preTurn.system : baseSystem;
|
|
151
|
+
|
|
114
152
|
let buffer = '';
|
|
115
153
|
let lastStopReason = null;
|
|
116
154
|
|
|
@@ -119,7 +157,7 @@ export function createRuntime({
|
|
|
119
157
|
const stream = currentProvider({
|
|
120
158
|
messages: messages.slice(),
|
|
121
159
|
tools: buildToolList(),
|
|
122
|
-
system:
|
|
160
|
+
system: turnSystem,
|
|
123
161
|
signal: abortCtrl.signal,
|
|
124
162
|
// turnEl + setReplyText let renderer-shaped providers (e.g. the
|
|
125
163
|
// local transformers wrapper) paint progress bars and <think>
|
|
@@ -176,13 +214,34 @@ export function createRuntime({
|
|
|
176
214
|
emit('toolResult', { turnEl, name: tu.name, ok: false, error: err, id: tu.id });
|
|
177
215
|
continue;
|
|
178
216
|
}
|
|
217
|
+
|
|
218
|
+
// preToolUse fires per tool_use before the handler runs. Handler
|
|
219
|
+
// can return { approve: false, reason } to short-circuit (useful
|
|
220
|
+
// for human-in-the-loop gating via askInChat) or { input } to
|
|
221
|
+
// mutate the args. Falling through preserves the original call.
|
|
222
|
+
const directive = await emitAsync('preToolUse', {
|
|
223
|
+
turnEl, name: tu.name, input: tu.input, id: tu.id,
|
|
224
|
+
});
|
|
225
|
+
if (directive?.approve === false) {
|
|
226
|
+
const reason = directive.reason || `Denied by preToolUse hook`;
|
|
227
|
+
toolResults.push({
|
|
228
|
+
type: 'tool_result',
|
|
229
|
+
tool_use_id: tu.id,
|
|
230
|
+
content: reason,
|
|
231
|
+
is_error: true,
|
|
232
|
+
});
|
|
233
|
+
emit('toolResult', { turnEl, name: tu.name, ok: false, error: reason, id: tu.id });
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const effectiveInput = directive?.input !== undefined ? directive.input : tu.input;
|
|
237
|
+
|
|
179
238
|
try {
|
|
180
239
|
const ctx = {
|
|
181
240
|
signal: abortCtrl.signal,
|
|
182
241
|
turnId: tu.id,
|
|
183
242
|
runtime: handle,
|
|
184
243
|
};
|
|
185
|
-
const result = await tool.handler(
|
|
244
|
+
const result = await tool.handler(effectiveInput, ctx);
|
|
186
245
|
const content = typeof result === 'string' ? result : JSON.stringify(result);
|
|
187
246
|
toolResults.push({ type: 'tool_result', tool_use_id: tu.id, content });
|
|
188
247
|
emit('toolResult', { turnEl, name: tu.name, ok: true, result, id: tu.id });
|
|
@@ -202,6 +261,13 @@ export function createRuntime({
|
|
|
202
261
|
|
|
203
262
|
trim();
|
|
204
263
|
persist();
|
|
264
|
+
emit('postTurn', {
|
|
265
|
+
turnEl,
|
|
266
|
+
userText,
|
|
267
|
+
added: messages.slice(startIdx),
|
|
268
|
+
text: buffer.trim(),
|
|
269
|
+
stopReason: lastStopReason,
|
|
270
|
+
});
|
|
205
271
|
return buffer.trim();
|
|
206
272
|
} catch (err) {
|
|
207
273
|
if (err?.name === 'AbortError') {
|