@code-yeongyu/senpi-ai 2026.10.25-4 → 2026.10.26-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/dist/api/anthropic-messages.js +19 -2
- package/dist/api/azure-openai-responses.js +1 -1
- package/dist/api/bedrock-converse-stream.js +13 -1
- package/dist/api/cursor-agent.js +2 -2
- package/dist/api/devin-agent/stream-state.js +8 -0
- package/dist/api/devin-agent.js +1 -1
- package/dist/api/google-generative-ai.js +22 -10
- package/dist/api/google-vertex.js +22 -10
- package/dist/api/lazy.js +8 -2
- package/dist/api/mistral-conversations.js +1 -1
- package/dist/api/openai-codex-responses.js +1 -1
- package/dist/api/openai-completions.js +9 -4
- package/dist/api/openai-responses-shared.js +37 -3
- package/dist/api/openai-responses.js +1 -1
- package/dist/api/pi-messages.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/providers/data/.manifest.json +1 -1
- package/dist/providers/data/amazon-bedrock.json +1 -1
- package/dist/providers/data/opengateway.json +1 -1
- package/dist/providers/data/openrouter.json +1 -1
- package/dist/providers/faux.js +5 -1
- package/dist/tool-call-middleware/protocols/kimi-xtml/thinking-recovery-stream.js +37 -2
- package/dist/tool-call-middleware/recovery-event-stream.d.ts +12 -1
- package/dist/tool-call-middleware/recovery-event-stream.js +30 -4
- package/dist/tool-call-middleware/recovery-native-projection.d.ts +10 -0
- package/dist/tool-call-middleware/recovery-native-projection.js +36 -7
- package/dist/tool-call-middleware/recovery-stream-failure.js +3 -2
- package/dist/tool-call-middleware/recovery-stream-terminal.d.ts +6 -0
- package/dist/tool-call-middleware/recovery-stream-terminal.js +35 -17
- package/dist/tool-call-middleware/recovery-stream-wrapper.js +26 -12
- package/dist/tool-call-middleware/stream-wrapper.js +3 -1
- package/dist/utils/assistant-capture.d.ts +101 -0
- package/dist/utils/assistant-capture.js +327 -0
- package/dist/utils/event-stream.d.ts +15 -2
- package/dist/utils/event-stream.js +57 -8
- package/dist/utils/tool-argument-coercion.d.ts +2 -0
- package/dist/utils/tool-argument-coercion.js +74 -0
- package/dist/utils/tool-argument-validator.d.ts +20 -0
- package/dist/utils/tool-argument-validator.js +9 -0
- package/dist/utils/tool-schema-compat.d.ts +5 -0
- package/dist/utils/tool-schema-compat.js +5 -0
- package/dist/utils/validation.d.ts +1 -0
- package/dist/utils/validation.js +14 -75
- package/package.json +2 -2
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
const captures = new WeakMap();
|
|
2
|
+
const readers = new WeakMap();
|
|
3
|
+
const writers = new WeakMap();
|
|
4
|
+
/** `undefined` until a capture-enabled stream's first push decides it, `null` once decided off. */
|
|
5
|
+
const states = new WeakMap();
|
|
6
|
+
const demanded = new WeakSet();
|
|
7
|
+
const warned = new WeakSet();
|
|
8
|
+
const NO_CHANGE = Object.freeze({});
|
|
9
|
+
let subscribers = 0;
|
|
10
|
+
let capturedAny = false;
|
|
11
|
+
/** While held, every capture-enabled stream whose first push comes after it builds capture records. */
|
|
12
|
+
export function subscribeAssistantCapture() {
|
|
13
|
+
subscribers++;
|
|
14
|
+
let held = true;
|
|
15
|
+
return () => {
|
|
16
|
+
if (held)
|
|
17
|
+
subscribers--;
|
|
18
|
+
held = false;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function setAssistantReader(owner, reader) {
|
|
22
|
+
readers.set(owner, reader);
|
|
23
|
+
}
|
|
24
|
+
export function setAssistantProgressWriter(owner, writer) {
|
|
25
|
+
writers.set(owner, writer);
|
|
26
|
+
}
|
|
27
|
+
export function enableAssistantCapture(owner) {
|
|
28
|
+
if (!states.has(owner))
|
|
29
|
+
states.set(owner, undefined);
|
|
30
|
+
}
|
|
31
|
+
/** Decided once, at the first push, so a stream never starts capturing midway with partial offsets. */
|
|
32
|
+
function activeState(owner) {
|
|
33
|
+
const state = states.get(owner);
|
|
34
|
+
if (state !== undefined || !states.has(owner))
|
|
35
|
+
return state ?? undefined;
|
|
36
|
+
const decided = subscribers > 0 || demanded.has(owner)
|
|
37
|
+
? { positions: new Map(), metadata: new Map(), insertCopies: new Map(), failedBlocks: new Set() }
|
|
38
|
+
: null;
|
|
39
|
+
capturedAny ||= decided !== null;
|
|
40
|
+
states.set(owner, decided);
|
|
41
|
+
return decided ?? undefined;
|
|
42
|
+
}
|
|
43
|
+
/** A capture failure never reaches the provider stream: the block loses its records and one warning is logged. */
|
|
44
|
+
function dropCapture(owner, error, state, contentIndex) {
|
|
45
|
+
if (contentIndex !== undefined)
|
|
46
|
+
state?.failedBlocks.add(contentIndex);
|
|
47
|
+
if (warned.has(owner))
|
|
48
|
+
return;
|
|
49
|
+
warned.add(owner);
|
|
50
|
+
console.warn("[assistant-capture] could not build an internal capture record; that block's records are dropped", error);
|
|
51
|
+
}
|
|
52
|
+
export function pushAssistantProgress(owner, progress) {
|
|
53
|
+
const writer = writers.get(owner);
|
|
54
|
+
if (!writer)
|
|
55
|
+
return;
|
|
56
|
+
// A forwarded record is already a frozen public copy, made by the stream that captured it.
|
|
57
|
+
if (Object.isFrozen(progress)) {
|
|
58
|
+
writer(progress);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const state = activeState(owner);
|
|
62
|
+
if (!state || state.failedBlocks.has(progress.contentIndex))
|
|
63
|
+
return;
|
|
64
|
+
try {
|
|
65
|
+
writer(freezeProgress(state, progress));
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
dropCapture(owner, error, state, progress.contentIndex);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function freezeProgress(state, progress) {
|
|
72
|
+
if (progress.type === "message_tool_arguments_reset")
|
|
73
|
+
state.positions.set(progress.contentIndex, {
|
|
74
|
+
offset: progress.rawArgumentPrefix.length,
|
|
75
|
+
parsedThrough: progress.parsedThrough,
|
|
76
|
+
});
|
|
77
|
+
if (progress.type !== "message_block_state")
|
|
78
|
+
return Object.freeze({ ...progress });
|
|
79
|
+
const previous = state.insertCopies.get(progress.contentIndex);
|
|
80
|
+
const current = publicBlock(progress.block);
|
|
81
|
+
const block = previous !== undefined && sameValue(current, previous) ? previous : freezeCapture(current);
|
|
82
|
+
if (progress.phase === "insert")
|
|
83
|
+
state.insertCopies.set(progress.contentIndex, block);
|
|
84
|
+
else
|
|
85
|
+
state.insertCopies.delete(progress.contentIndex);
|
|
86
|
+
return Object.freeze({ ...progress, block });
|
|
87
|
+
}
|
|
88
|
+
/** A text or thinking signature replaced without a public event; an unchanged or empty one records nothing. */
|
|
89
|
+
export function pushAssistantSignature(owner, contentIndex, block, signature) {
|
|
90
|
+
if (!signature || signature === (block.type === "text" ? block.textSignature : block.thinkingSignature))
|
|
91
|
+
return;
|
|
92
|
+
pushAssistantProgress(owner, {
|
|
93
|
+
type: "message_block_metadata",
|
|
94
|
+
contentIndex,
|
|
95
|
+
...(block.type === "text" ? { textSignature: signature } : { thinkingSignature: signature }),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/** A block appended complete with no public event: its unchanged end reuses the insert's frozen copy. */
|
|
99
|
+
export function pushAssistantCompleteBlock(owner, contentIndex, block) {
|
|
100
|
+
for (const phase of ["insert", "end"])
|
|
101
|
+
pushAssistantProgress(owner, { type: "message_block_state", contentIndex, phase, block });
|
|
102
|
+
}
|
|
103
|
+
/** Internal consumption is explicit; ordinary iterators are the downgrade boundary. Attached before the first push, it turns capture on. */
|
|
104
|
+
export function assistantRecords(source) {
|
|
105
|
+
demanded.add(source);
|
|
106
|
+
const read = readers.get(source);
|
|
107
|
+
return {
|
|
108
|
+
[Symbol.asyncIterator]: () => read?.((entry) => ({
|
|
109
|
+
value: "event" in entry ? entry.event : entry.progress,
|
|
110
|
+
done: false,
|
|
111
|
+
})) ?? source[Symbol.asyncIterator](),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/** Events only; each progress record goes to `onProgress` as it is read, so a forwarder spends no turn on it. */
|
|
115
|
+
export function assistantEvents(source, onProgress) {
|
|
116
|
+
const read = readers.get(source);
|
|
117
|
+
return {
|
|
118
|
+
[Symbol.asyncIterator]: () => read?.((entry) => {
|
|
119
|
+
if ("event" in entry)
|
|
120
|
+
return { value: entry.event, done: false };
|
|
121
|
+
onProgress(entry.progress);
|
|
122
|
+
return undefined;
|
|
123
|
+
}) ?? source[Symbol.asyncIterator](),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/** Internal readers address a block by `capture.contentIndex ?? event.contentIndex`; the public index can drift (Google/Vertex). */
|
|
127
|
+
export function getAssistantCapture(event) {
|
|
128
|
+
return captures.get(event);
|
|
129
|
+
}
|
|
130
|
+
export function isAssistantProgress(event) {
|
|
131
|
+
return (event.type === "message_block_state" ||
|
|
132
|
+
event.type === "message_block_metadata" ||
|
|
133
|
+
event.type === "message_tool_arguments_reset");
|
|
134
|
+
}
|
|
135
|
+
export function freezeCapture(value) {
|
|
136
|
+
const freeze = (part) => {
|
|
137
|
+
if (part === null || typeof part !== "object" || Object.isFrozen(part))
|
|
138
|
+
return;
|
|
139
|
+
for (const child of Object.values(part))
|
|
140
|
+
freeze(child);
|
|
141
|
+
Object.freeze(part);
|
|
142
|
+
};
|
|
143
|
+
const copy = structuredClone(value);
|
|
144
|
+
freeze(copy);
|
|
145
|
+
return copy;
|
|
146
|
+
}
|
|
147
|
+
/** Only declared public fields survive; provider scratch and symbols never enter a seed. */
|
|
148
|
+
export function publicBlock(block) {
|
|
149
|
+
const fields = {
|
|
150
|
+
text: ["type", "text", "audience", "textSignature"],
|
|
151
|
+
thinking: ["type", "thinking", "thinkingSignature", "redacted", "startedAt", "endedAt"],
|
|
152
|
+
toolCall: ["type", "id", "name", "arguments", "incomplete", "errorMessage", "thoughtSignature", "namespace"],
|
|
153
|
+
providerNative: ["type", "subtype", "raw"],
|
|
154
|
+
};
|
|
155
|
+
return Object.fromEntries(fields[block.type].filter((key) => key in block).map((key) => [key, Reflect.get(block, key)]));
|
|
156
|
+
}
|
|
157
|
+
/** Structural equality over plain data; any other object counts as changed, so a reused copy is never stale. */
|
|
158
|
+
function sameValue(left, right) {
|
|
159
|
+
if (left === right)
|
|
160
|
+
return true;
|
|
161
|
+
if (!isPlainData(left) || !isPlainData(right) || Array.isArray(left) !== Array.isArray(right))
|
|
162
|
+
return false;
|
|
163
|
+
const keys = Object.keys(left);
|
|
164
|
+
return (keys.length === Object.keys(right).length &&
|
|
165
|
+
keys.every((key) => Object.hasOwn(right, key) && sameValue(Reflect.get(left, key), Reflect.get(right, key))));
|
|
166
|
+
}
|
|
167
|
+
function isPlainData(value) {
|
|
168
|
+
if (typeof value !== "object" || value === null)
|
|
169
|
+
return false;
|
|
170
|
+
const prototype = Object.getPrototypeOf(value);
|
|
171
|
+
return prototype === Object.prototype || prototype === Array.prototype || prototype === null;
|
|
172
|
+
}
|
|
173
|
+
export function dropAssistantCapture(event) {
|
|
174
|
+
if (capturedAny)
|
|
175
|
+
captures.delete(event);
|
|
176
|
+
}
|
|
177
|
+
export function carryAssistantCapture(source, target, terminal) {
|
|
178
|
+
const capture = captures.get(source);
|
|
179
|
+
if (!capture)
|
|
180
|
+
return target;
|
|
181
|
+
try {
|
|
182
|
+
captures.set(target, terminal ? terminalCapture(capture, terminal) : capture);
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
dropCapture(source, error);
|
|
186
|
+
}
|
|
187
|
+
return target;
|
|
188
|
+
}
|
|
189
|
+
/** A terminal replaced downstream: its usage and message, plus every scalar that differs from the source terminal. */
|
|
190
|
+
function terminalCapture(capture, terminal) {
|
|
191
|
+
const metadata = { ...capture.metadata };
|
|
192
|
+
const previous = capture.message ?? {};
|
|
193
|
+
for (const key of new Set([...Object.keys(previous), ...Object.keys(terminal)])) {
|
|
194
|
+
if (key === "content" || key === "usage")
|
|
195
|
+
continue;
|
|
196
|
+
const value = Reflect.get(terminal, key);
|
|
197
|
+
if (!sameValue(value, Reflect.get(previous, key)))
|
|
198
|
+
metadata[key] = key in terminal ? value : null;
|
|
199
|
+
}
|
|
200
|
+
return Object.freeze({
|
|
201
|
+
usage: frozenUsage(terminal.usage),
|
|
202
|
+
metadata: freezeCapture(metadata),
|
|
203
|
+
message: freezeCapture({ ...terminal, content: terminal.content.map(publicBlock) }),
|
|
204
|
+
...(capture.rewrittenFrom === undefined ? {} : { rewrittenFrom: capture.rewrittenFrom }),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/** Never parses or traverses a block on a delta, including a no-growth delta. */
|
|
208
|
+
export function captureAssistantEvent(owner, event, hint = {}) {
|
|
209
|
+
const state = activeState(owner);
|
|
210
|
+
// A relay of a source that captured nothing, or captured a drifted index, gets no record: lack, never wrong.
|
|
211
|
+
if (!state || captures.has(event) || hint.carried === null || hint.carried?.contentIndex !== undefined)
|
|
212
|
+
return;
|
|
213
|
+
const contentIndex = "contentIndex" in event ? (hint.contentIndex ?? event.contentIndex) : undefined;
|
|
214
|
+
if (contentIndex !== undefined && state.failedBlocks.has(contentIndex))
|
|
215
|
+
return;
|
|
216
|
+
try {
|
|
217
|
+
const capture = buildCapture(state, event, hint);
|
|
218
|
+
if (capture)
|
|
219
|
+
captures.set(event, capture);
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
dropCapture(owner, error, state, contentIndex);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function buildCapture(state, event, hint) {
|
|
226
|
+
const message = event.type === "done" ? event.message : event.type === "error" ? event.error : event.partial;
|
|
227
|
+
const usage = usageCopy(state, message.usage);
|
|
228
|
+
if (!("contentIndex" in event)) {
|
|
229
|
+
return Object.freeze({
|
|
230
|
+
usage,
|
|
231
|
+
metadata: changedMetadata(state, message),
|
|
232
|
+
message: freezeCapture({ ...message, content: message.content.map(publicBlock) }),
|
|
233
|
+
...(hint.rewrittenFrom === undefined ? {} : { rewrittenFrom: hint.rewrittenFrom }),
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
const index = hint.contentIndex ?? event.contentIndex;
|
|
237
|
+
const own = index === event.contentIndex ? {} : { contentIndex: index };
|
|
238
|
+
const carried = hint.carried;
|
|
239
|
+
if ("delta" in event) {
|
|
240
|
+
if (carried) {
|
|
241
|
+
const { parsedThrough } = carried;
|
|
242
|
+
return Object.freeze({
|
|
243
|
+
usage,
|
|
244
|
+
offset: carried.offset,
|
|
245
|
+
...(parsedThrough === undefined ? {} : { parsedThrough }),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
const position = state.positions.get(index) ?? { offset: 0, parsedThrough: 0 };
|
|
249
|
+
const offset = hint.offset ?? position.offset;
|
|
250
|
+
position.offset = offset + event.delta.length;
|
|
251
|
+
if (event.type === "toolcall_delta" && event.delta.length > 0) {
|
|
252
|
+
position.parsedThrough = hint.parsedThrough ?? Math.max(position.parsedThrough, position.offset);
|
|
253
|
+
}
|
|
254
|
+
state.positions.set(index, position);
|
|
255
|
+
return Object.freeze({
|
|
256
|
+
usage,
|
|
257
|
+
offset,
|
|
258
|
+
...own,
|
|
259
|
+
...(event.type === "toolcall_delta" ? { parsedThrough: position.parsedThrough } : {}),
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
const metadata = changedMetadata(state, message);
|
|
263
|
+
if (carried) {
|
|
264
|
+
const { usage: _usage, metadata: _metadata, message: _message, offset: _offset, ...facts } = carried;
|
|
265
|
+
return Object.freeze({ ...facts, usage, metadata });
|
|
266
|
+
}
|
|
267
|
+
const starts = event.type === "text_start" || event.type === "thinking_start" || event.type === "toolcall_start";
|
|
268
|
+
const source = event.type === "toolcall_end" ? event.toolCall : message.content[index];
|
|
269
|
+
// A start announced before its block is appended has no seed to freeze yet.
|
|
270
|
+
if (starts && source === undefined)
|
|
271
|
+
return undefined;
|
|
272
|
+
const block = freezeCapture(publicBlock(source));
|
|
273
|
+
if (!starts)
|
|
274
|
+
return Object.freeze({ usage, metadata, block, ...own });
|
|
275
|
+
const rawArgumentPrefix = hint.rawArgumentPrefix ?? "";
|
|
276
|
+
const coveredCodeUnits = block.type === "text"
|
|
277
|
+
? block.text.length
|
|
278
|
+
: block.type === "thinking"
|
|
279
|
+
? block.thinking.length
|
|
280
|
+
: rawArgumentPrefix.length;
|
|
281
|
+
const parsedThrough = hint.parsedThrough ?? 0;
|
|
282
|
+
state.positions.set(index, { offset: hint.offset ?? coveredCodeUnits, parsedThrough });
|
|
283
|
+
return Object.freeze({
|
|
284
|
+
usage,
|
|
285
|
+
metadata,
|
|
286
|
+
block,
|
|
287
|
+
coveredCodeUnits,
|
|
288
|
+
...own,
|
|
289
|
+
...(block.type === "toolCall" ? { rawArgumentPrefix, parsedThrough } : {}),
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
function usageCopy(state, usage) {
|
|
293
|
+
if (state.usage !== undefined && sameValue(usage, state.usage))
|
|
294
|
+
return state.usage;
|
|
295
|
+
state.usage = frozenUsage(usage);
|
|
296
|
+
return state.usage;
|
|
297
|
+
}
|
|
298
|
+
function frozenUsage(usage) {
|
|
299
|
+
return Object.freeze({
|
|
300
|
+
...usage,
|
|
301
|
+
cost: Object.freeze({ ...usage.cost }),
|
|
302
|
+
...(usage.contextUsage ? { contextUsage: Object.freeze({ ...usage.contextUsage }) } : {}),
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
/** Scalar message fields changed since the last non-delta capture; the baseline moves only once its copy exists. */
|
|
306
|
+
function changedMetadata(state, message) {
|
|
307
|
+
const changed = {};
|
|
308
|
+
for (const key of Object.keys(message)) {
|
|
309
|
+
if (key === "content" || key === "usage")
|
|
310
|
+
continue;
|
|
311
|
+
const value = Reflect.get(message, key);
|
|
312
|
+
if (!state.metadata.has(key) || state.metadata.get(key) !== value)
|
|
313
|
+
changed[key] = value;
|
|
314
|
+
}
|
|
315
|
+
for (const key of state.metadata.keys())
|
|
316
|
+
if (!(key in message))
|
|
317
|
+
changed[key] = null;
|
|
318
|
+
const frozen = Object.keys(changed).length === 0 ? NO_CHANGE : freezeCapture(changed);
|
|
319
|
+
for (const key of Object.keys(changed)) {
|
|
320
|
+
if (key in message)
|
|
321
|
+
state.metadata.set(key, Reflect.get(message, key));
|
|
322
|
+
else
|
|
323
|
+
state.metadata.delete(key);
|
|
324
|
+
}
|
|
325
|
+
return frozen;
|
|
326
|
+
}
|
|
327
|
+
//# sourceMappingURL=assistant-capture.js.map
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import type { AssistantMessage, AssistantMessageEvent } from "../types.ts";
|
|
2
|
+
import { type AssistantProgress, type CaptureHint } from "./assistant-capture.ts";
|
|
3
|
+
type OrderedEntry<T> = {
|
|
4
|
+
event: T;
|
|
5
|
+
} | {
|
|
6
|
+
progress: AssistantProgress;
|
|
7
|
+
};
|
|
2
8
|
export declare class EventStream<T, R = T> implements AsyncIterable<T> {
|
|
3
9
|
#private;
|
|
4
10
|
private waiting;
|
|
5
|
-
|
|
11
|
+
protected done: boolean;
|
|
6
12
|
private finalResultPromise;
|
|
7
13
|
private resolveFinalResult;
|
|
8
14
|
private rejectFinalResult;
|
|
@@ -11,9 +17,11 @@ export declare class EventStream<T, R = T> implements AsyncIterable<T> {
|
|
|
11
17
|
constructor(isComplete: (event: T) => boolean, extractResult: (event: T) => R);
|
|
12
18
|
get queue(): T[];
|
|
13
19
|
push(event: T): void;
|
|
20
|
+
protected enqueueProgress(progress: AssistantProgress): void;
|
|
14
21
|
end(result?: R): void;
|
|
15
22
|
fail(error: unknown): void;
|
|
16
23
|
[Symbol.asyncIterator](): AsyncIterator<T>;
|
|
24
|
+
protected mapIterator<V>(project: (entry: OrderedEntry<T>) => IteratorResult<V> | undefined): AsyncIterator<V>;
|
|
17
25
|
result(): Promise<R>;
|
|
18
26
|
/**
|
|
19
27
|
* Track a locally executing unit of work (for example a server-requested
|
|
@@ -27,8 +35,13 @@ export declare class EventStream<T, R = T> implements AsyncIterable<T> {
|
|
|
27
35
|
hasPendingLocalWork(): boolean;
|
|
28
36
|
}
|
|
29
37
|
export declare class AssistantMessageEventStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
|
|
30
|
-
constructor(
|
|
38
|
+
constructor(options?: {
|
|
39
|
+
readonly captureBlocks?: boolean;
|
|
40
|
+
});
|
|
41
|
+
push(event: AssistantMessageEvent, hint?: CaptureHint): void;
|
|
42
|
+
[Symbol.asyncIterator](): AsyncIterator<AssistantMessageEvent>;
|
|
31
43
|
}
|
|
32
44
|
/** Factory function for AssistantMessageEventStream (for use in extensions) */
|
|
33
45
|
export declare function createAssistantMessageEventStream(): AssistantMessageEventStream;
|
|
46
|
+
export {};
|
|
34
47
|
//# sourceMappingURL=event-stream.d.ts.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { captureAssistantEvent, dropAssistantCapture, enableAssistantCapture, setAssistantProgressWriter, setAssistantReader, } from "./assistant-capture.js";
|
|
1
2
|
class FifoQueue {
|
|
2
3
|
constructor() {
|
|
3
4
|
this.incoming = [];
|
|
@@ -9,6 +10,9 @@ class FifoQueue {
|
|
|
9
10
|
enqueue(value) {
|
|
10
11
|
this.incoming.push(value);
|
|
11
12
|
}
|
|
13
|
+
prepend(value) {
|
|
14
|
+
this.outgoing.push(value);
|
|
15
|
+
}
|
|
12
16
|
dequeue() {
|
|
13
17
|
if (this.outgoing.length === 0) {
|
|
14
18
|
while (this.incoming.length > 0) {
|
|
@@ -41,7 +45,7 @@ export class EventStream {
|
|
|
41
45
|
this.finalResultPromise.catch(() => { });
|
|
42
46
|
}
|
|
43
47
|
get queue() {
|
|
44
|
-
return this.#queue.slice(this.#queueHead);
|
|
48
|
+
return this.#queue.slice(this.#queueHead).flatMap((entry) => ("event" in entry ? [entry.event] : []));
|
|
45
49
|
}
|
|
46
50
|
#enqueue(event) {
|
|
47
51
|
this.#queue.push(event);
|
|
@@ -65,13 +69,19 @@ export class EventStream {
|
|
|
65
69
|
this.done = true;
|
|
66
70
|
this.resolveFinalResult(this.extractResult(event));
|
|
67
71
|
}
|
|
68
|
-
|
|
72
|
+
this.#deliver({ event });
|
|
73
|
+
}
|
|
74
|
+
enqueueProgress(progress) {
|
|
75
|
+
if (!this.done)
|
|
76
|
+
this.#deliver({ progress });
|
|
77
|
+
}
|
|
78
|
+
#deliver(entry) {
|
|
69
79
|
const waiter = this.waiting.dequeue();
|
|
70
80
|
if (waiter) {
|
|
71
|
-
waiter.resolve({ value:
|
|
81
|
+
waiter.resolve({ value: entry, done: false });
|
|
72
82
|
}
|
|
73
83
|
else {
|
|
74
|
-
this.#enqueue(
|
|
84
|
+
this.#enqueue(entry);
|
|
75
85
|
}
|
|
76
86
|
}
|
|
77
87
|
end(result) {
|
|
@@ -100,10 +110,15 @@ export class EventStream {
|
|
|
100
110
|
}
|
|
101
111
|
}
|
|
102
112
|
[Symbol.asyncIterator]() {
|
|
113
|
+
return this.mapIterator((entry) => ("event" in entry ? { value: entry.event, done: false } : undefined));
|
|
114
|
+
}
|
|
115
|
+
mapIterator(project) {
|
|
103
116
|
return {
|
|
104
117
|
next: () => {
|
|
105
|
-
|
|
106
|
-
|
|
118
|
+
while (this.#queueHead < this.#queue.length) {
|
|
119
|
+
const result = project(this.#dequeue());
|
|
120
|
+
if (result)
|
|
121
|
+
return Promise.resolve(result);
|
|
107
122
|
}
|
|
108
123
|
if (this.#failed) {
|
|
109
124
|
return Promise.reject(this.#error);
|
|
@@ -111,7 +126,19 @@ export class EventStream {
|
|
|
111
126
|
if (this.done) {
|
|
112
127
|
return Promise.resolve({ value: undefined, done: true });
|
|
113
128
|
}
|
|
114
|
-
return new Promise((resolve, reject) =>
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
const waiter = {
|
|
131
|
+
reject,
|
|
132
|
+
resolve: (next) => {
|
|
133
|
+
const result = next.done ? { value: undefined, done: true } : project(next.value);
|
|
134
|
+
if (result)
|
|
135
|
+
resolve(result);
|
|
136
|
+
else
|
|
137
|
+
this.waiting.prepend(waiter);
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
this.waiting.enqueue(waiter);
|
|
141
|
+
});
|
|
115
142
|
},
|
|
116
143
|
};
|
|
117
144
|
}
|
|
@@ -140,7 +167,7 @@ export class EventStream {
|
|
|
140
167
|
}
|
|
141
168
|
}
|
|
142
169
|
export class AssistantMessageEventStream extends EventStream {
|
|
143
|
-
constructor() {
|
|
170
|
+
constructor(options = {}) {
|
|
144
171
|
super((event) => event.type === "done" || event.type === "error", (event) => {
|
|
145
172
|
if (event.type === "done") {
|
|
146
173
|
return event.message;
|
|
@@ -150,6 +177,28 @@ export class AssistantMessageEventStream extends EventStream {
|
|
|
150
177
|
}
|
|
151
178
|
throw new Error("Unexpected event type for final result");
|
|
152
179
|
});
|
|
180
|
+
if (options.captureBlocks)
|
|
181
|
+
enableAssistantCapture(this);
|
|
182
|
+
setAssistantProgressWriter(this, (progress) => this.enqueueProgress(progress));
|
|
183
|
+
// A subclass that replaces iteration (cancellation, snapshots, remapping) stays unaware unless it registers a reader;
|
|
184
|
+
// one that remaps contentIndex in push must register its own reader too, or progress keeps the unmapped indices.
|
|
185
|
+
if (new.target.prototype[Symbol.asyncIterator] === AssistantMessageEventStream.prototype[Symbol.asyncIterator])
|
|
186
|
+
setAssistantReader(this, (project) => this.mapIterator(project));
|
|
187
|
+
}
|
|
188
|
+
push(event, hint) {
|
|
189
|
+
if (this.done)
|
|
190
|
+
return;
|
|
191
|
+
captureAssistantEvent(this, event, hint);
|
|
192
|
+
super.push(event);
|
|
193
|
+
}
|
|
194
|
+
[Symbol.asyncIterator]() {
|
|
195
|
+
return this.mapIterator((entry) => {
|
|
196
|
+
if (!("event" in entry))
|
|
197
|
+
return undefined;
|
|
198
|
+
// Unaware wrappers may snapshot, remap, buffer or discard this event. Never forward its carrier.
|
|
199
|
+
dropAssistantCapture(entry.event);
|
|
200
|
+
return { value: entry.event, done: false };
|
|
201
|
+
});
|
|
153
202
|
}
|
|
154
203
|
}
|
|
155
204
|
/** Factory function for AssistantMessageEventStream (for use in extensions) */
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export function coercePrimitiveByType(value, type) {
|
|
2
|
+
switch (type) {
|
|
3
|
+
case "number": {
|
|
4
|
+
if (value === null) {
|
|
5
|
+
return 0;
|
|
6
|
+
}
|
|
7
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
8
|
+
const parsed = Number(value);
|
|
9
|
+
if (Number.isFinite(parsed)) {
|
|
10
|
+
return parsed;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
if (typeof value === "boolean") {
|
|
14
|
+
return value ? 1 : 0;
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
case "integer": {
|
|
19
|
+
if (value === null) {
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
23
|
+
const parsed = Number(value);
|
|
24
|
+
if (Number.isInteger(parsed)) {
|
|
25
|
+
return parsed;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (typeof value === "boolean") {
|
|
29
|
+
return value ? 1 : 0;
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
case "boolean": {
|
|
34
|
+
if (value === null) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
if (typeof value === "string") {
|
|
38
|
+
if (value === "true") {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
if (value === "false") {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (typeof value === "number") {
|
|
46
|
+
if (value === 1) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
if (value === 0) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
case "string": {
|
|
56
|
+
if (value === null) {
|
|
57
|
+
return "";
|
|
58
|
+
}
|
|
59
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
60
|
+
return String(value);
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
case "null": {
|
|
65
|
+
if (value === "" || value === 0 || value === false) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
default:
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=tool-argument-coercion.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { TSchema } from "typebox";
|
|
2
|
+
export interface ToolArgumentValidationError {
|
|
3
|
+
readonly instancePath: string;
|
|
4
|
+
readonly keyword: string;
|
|
5
|
+
readonly message?: string;
|
|
6
|
+
readonly params: {
|
|
7
|
+
readonly [key: string]: unknown;
|
|
8
|
+
readonly missingProperty?: string;
|
|
9
|
+
readonly requiredProperties?: string[];
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
interface ToolArgumentValidator {
|
|
13
|
+
(value: unknown): boolean;
|
|
14
|
+
errors?: readonly ToolArgumentValidationError[] | null;
|
|
15
|
+
}
|
|
16
|
+
/** A protocol may own validation without permitting local argument coercion. */
|
|
17
|
+
export declare function registerToolArgumentValidator(schema: TSchema, validate: ToolArgumentValidator): void;
|
|
18
|
+
export declare function getToolArgumentValidator(schema: TSchema): ToolArgumentValidator | undefined;
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=tool-argument-validator.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const validators = new WeakMap();
|
|
2
|
+
/** A protocol may own validation without permitting local argument coercion. */
|
|
3
|
+
export function registerToolArgumentValidator(schema, validate) {
|
|
4
|
+
validators.set(schema, validate);
|
|
5
|
+
}
|
|
6
|
+
export function getToolArgumentValidator(schema) {
|
|
7
|
+
return validators.get(schema);
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=tool-argument-validator.js.map
|
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
* subset that rejects a sibling `type` keyword on schemas that also declare
|
|
5
5
|
* `anyOf` / `oneOf` / `allOf`. This helper removes that redundancy while
|
|
6
6
|
* preserving the schema's semantics.
|
|
7
|
+
*
|
|
8
|
+
* This is provider wire adaptation, not an MCP schema conversion or argument
|
|
9
|
+
* validator. MCP supplies its full schema (boolean roots use the equivalent
|
|
10
|
+
* allOf container); the original schema and model arguments are never rewritten.
|
|
11
|
+
* Root-union conversion below is explicitly lossy for object-only providers.
|
|
7
12
|
*/
|
|
8
13
|
export type ToolSchemaFlavor = "moonshot-mfjs";
|
|
9
14
|
/**
|
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
* subset that rejects a sibling `type` keyword on schemas that also declare
|
|
5
5
|
* `anyOf` / `oneOf` / `allOf`. This helper removes that redundancy while
|
|
6
6
|
* preserving the schema's semantics.
|
|
7
|
+
*
|
|
8
|
+
* This is provider wire adaptation, not an MCP schema conversion or argument
|
|
9
|
+
* validator. MCP supplies its full schema (boolean roots use the equivalent
|
|
10
|
+
* allOf container); the original schema and model arguments are never rewritten.
|
|
11
|
+
* Root-union conversion below is explicitly lossy for object-only providers.
|
|
7
12
|
*/
|
|
8
13
|
const COMBINER_KEYS = ["anyOf", "oneOf", "allOf"];
|
|
9
14
|
const SCHEMA_MAP_KEYS = ["properties", "patternProperties", "$defs", "definitions"];
|