@ouro.bot/cli 0.1.0-alpha.724 → 0.1.0-alpha.725
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/changelog.json +12 -0
- package/dist/heart/active-work.js +10 -3
- package/dist/heart/core.js +343 -77
- package/dist/heart/daemon/cli-exec.js +72 -14
- package/dist/heart/daemon/cli-help.js +13 -2
- package/dist/heart/daemon/cli-parse.js +82 -1
- package/dist/heart/daemon/daemon.js +286 -33
- package/dist/heart/daemon/doctor.js +21 -6
- package/dist/heart/daemon/habit-private-wake.js +6 -1
- package/dist/heart/habits/habit-cancel.js +810 -0
- package/dist/heart/habits/habit-lifecycle.js +1327 -0
- package/dist/heart/habits/habit-migration.js +28 -23
- package/dist/heart/habits/habit-parser.js +72 -8
- package/dist/heart/habits/habit-runtime-state.js +3 -24
- package/dist/heart/habits/habit-scheduler.js +268 -50
- package/dist/heart/habits/habit-session.js +16 -0
- package/dist/heart/hatch/hatch-flow.js +6 -4
- package/dist/heart/mailbox/readers/runtime-readers.js +26 -27
- package/dist/heart/orientation-frame.js +58 -1
- package/dist/heart/provider-attempt.js +4 -1
- package/dist/heart/providers/anthropic.js +23 -2
- package/dist/heart/start-of-turn-packet.js +14 -11
- package/dist/heart/streaming.js +565 -185
- package/dist/mind/obligation-steering.js +15 -9
- package/dist/mind/prompt.js +58 -26
- package/dist/nerves/coverage/run-artifacts.js +55 -35
- package/dist/nerves/review/cli.js +2 -2
- package/dist/nerves/review/core.js +14 -7
- package/dist/repertoire/tools-base.js +2 -0
- package/dist/repertoire/tools-habits.js +103 -0
- package/dist/repertoire/tools.js +18 -3
- package/dist/rsvp/cli.js +126 -20
- package/dist/rsvp/diagnostics.js +12 -2
- package/dist/rsvp/habit-policy.js +7 -0
- package/dist/rsvp/habit-stage.js +19 -4
- package/dist/rsvp/native-habit-runner.js +151 -11
- package/dist/rsvp/outbound-state.js +360 -0
- package/dist/senses/bluebubbles/client.js +90 -17
- package/dist/senses/bluebubbles/inbound-log.js +1 -0
- package/dist/senses/bluebubbles/index.js +662 -425
- package/dist/senses/bluebubbles/model.js +57 -13
- package/dist/senses/bluebubbles/mutation-log.js +21 -10
- package/dist/senses/bluebubbles/reaction-policy.js +55 -0
- package/dist/senses/bluebubbles/semantic-receipts.js +1818 -0
- package/dist/senses/cli/tui-store.js +1 -0
- package/dist/senses/cli.js +1 -0
- package/dist/senses/habit-lifecycle-guard.js +24 -0
- package/dist/senses/ingress-evidence.js +2 -0
- package/dist/senses/pipeline.js +90 -24
- package/dist/senses/private-runtime-worker.js +21 -20
- package/dist/senses/private-runtime.js +79 -29
- package/dist/senses/shared-turn.js +1 -0
- package/dist/senses/teams.js +1 -0
- package/package.json +6 -1
package/dist/heart/streaming.js
CHANGED
|
@@ -1,80 +1,285 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RESPONSES_FUNCTION_CALL_OUTPUT_CAP = exports.SettleStreamer = exports.SettleParser = void 0;
|
|
3
|
+
exports.RESPONSES_FUNCTION_CALL_OUTPUT_CAP = exports.ProviderTextOutput = exports.SettleStreamer = exports.SettleParser = exports.SettleFinalizationCallbackError = void 0;
|
|
4
|
+
exports.finalizeSettleStream = finalizeSettleStream;
|
|
4
5
|
exports.truncateResponsesFunctionCallOutput = truncateResponsesFunctionCallOutput;
|
|
5
6
|
exports.toResponsesInput = toResponsesInput;
|
|
6
7
|
exports.toResponsesTools = toResponsesTools;
|
|
7
8
|
exports.streamChatCompletion = streamChatCompletion;
|
|
8
9
|
exports.streamResponsesApi = streamResponsesApi;
|
|
9
10
|
const runtime_1 = require("../nerves/runtime");
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
class SettleFinalizationCallbackError extends Error {
|
|
12
|
+
retryable = false;
|
|
13
|
+
cause;
|
|
14
|
+
constructor(cause) {
|
|
15
|
+
const normalized = cause instanceof Error ? cause : new Error(String(cause));
|
|
16
|
+
super(`settle finalization callback failed: ${normalized.message}`);
|
|
17
|
+
this.name = "SettleFinalizationCallbackError";
|
|
18
|
+
this.cause = normalized;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.SettleFinalizationCallbackError = SettleFinalizationCallbackError;
|
|
22
|
+
function isJsonWhitespace(character) {
|
|
23
|
+
return character === " " || character === "\t" || character === "\n" || character === "\r";
|
|
24
|
+
}
|
|
25
|
+
function isHighSurrogate(character) {
|
|
26
|
+
const code = character.charCodeAt(0);
|
|
27
|
+
return code >= 0xD800 && code <= 0xDBFF;
|
|
28
|
+
}
|
|
29
|
+
function isLowSurrogate(character) {
|
|
30
|
+
const code = character.charCodeAt(0);
|
|
31
|
+
return code >= 0xDC00 && code <= 0xDFFF;
|
|
32
|
+
}
|
|
33
|
+
// Incremental JSON string decoder for the top-level `answer` property in
|
|
34
|
+
// streamed settle arguments. JSON.parse remains the terminal authority; this
|
|
35
|
+
// state machine exists only to emit validated string units before EOF.
|
|
14
36
|
class SettleParser {
|
|
15
|
-
|
|
16
|
-
static PREFIXES = ['"answer":"', '"answer": "'];
|
|
17
|
-
// Buffer of characters seen so far (pre-activation only)
|
|
18
|
-
buf = "";
|
|
37
|
+
rawArguments = "";
|
|
19
38
|
_active = false;
|
|
20
39
|
_complete = false;
|
|
21
|
-
|
|
40
|
+
invalid = false;
|
|
41
|
+
terminalResult;
|
|
42
|
+
containers = [];
|
|
43
|
+
rootLastSignificant = "";
|
|
44
|
+
scanInString = false;
|
|
45
|
+
scanEscape = false;
|
|
46
|
+
scanStringRaw = "";
|
|
47
|
+
scanStringIsRootKey = false;
|
|
48
|
+
answerKeyState = "none";
|
|
49
|
+
decodeState = "normal";
|
|
50
|
+
unicodeDigits = "";
|
|
51
|
+
pendingHighSurrogate = "";
|
|
52
|
+
decodedAnswer = "";
|
|
22
53
|
get active() { return this._active; }
|
|
23
54
|
get complete() { return this._complete; }
|
|
24
55
|
process(delta) {
|
|
25
|
-
if (this.
|
|
56
|
+
if (this.terminalResult)
|
|
57
|
+
return "";
|
|
58
|
+
this.rawArguments += delta;
|
|
59
|
+
if (this._complete || this.invalid)
|
|
26
60
|
return "";
|
|
27
61
|
let out = "";
|
|
28
62
|
for (let i = 0; i < delta.length; i++) {
|
|
29
63
|
const ch = delta[i];
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
// Check if any prefix has been fully matched in the buffer
|
|
33
|
-
for (const prefix of SettleParser.PREFIXES) {
|
|
34
|
-
if (this.buf.endsWith(prefix)) {
|
|
35
|
-
this._active = true;
|
|
36
|
-
break;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
64
|
+
if (this._active) {
|
|
65
|
+
out += this.processAnswerCharacter(ch);
|
|
39
66
|
}
|
|
40
67
|
else {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
68
|
+
this.scanForTopLevelAnswer(ch);
|
|
69
|
+
}
|
|
70
|
+
if (this._complete || this.invalid)
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
finish() {
|
|
76
|
+
if (this.terminalResult)
|
|
77
|
+
return this.terminalResult;
|
|
78
|
+
if (this.invalid) {
|
|
79
|
+
return this.setTerminalFailure("invalid_settle_arguments");
|
|
80
|
+
}
|
|
81
|
+
let parsed;
|
|
82
|
+
try {
|
|
83
|
+
parsed = JSON.parse(this.rawArguments);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
const errorCode = this.isIncompleteAtEof(error)
|
|
87
|
+
? "incomplete_settle_arguments"
|
|
88
|
+
: "invalid_settle_arguments";
|
|
89
|
+
return this.setTerminalFailure(errorCode);
|
|
90
|
+
}
|
|
91
|
+
if (typeof parsed !== "object"
|
|
92
|
+
|| parsed === null
|
|
93
|
+
|| Array.isArray(parsed)
|
|
94
|
+
|| typeof parsed.answer !== "string"
|
|
95
|
+
|| !this._active
|
|
96
|
+
|| !this._complete) {
|
|
97
|
+
return this.setTerminalFailure("invalid_settle_arguments");
|
|
98
|
+
}
|
|
99
|
+
const answer = parsed.answer;
|
|
100
|
+
if (answer !== this.decodedAnswer) {
|
|
101
|
+
return this.setTerminalFailure("invalid_settle_arguments");
|
|
102
|
+
}
|
|
103
|
+
this.terminalResult = { ok: true, answer };
|
|
104
|
+
return this.terminalResult;
|
|
105
|
+
}
|
|
106
|
+
scanForTopLevelAnswer(character) {
|
|
107
|
+
if (this.scanInString) {
|
|
108
|
+
if (this.scanEscape) {
|
|
109
|
+
this.scanStringRaw += character;
|
|
110
|
+
this.scanEscape = false;
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (character === "\\") {
|
|
114
|
+
this.scanStringRaw += character;
|
|
115
|
+
this.scanEscape = true;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (character !== '"') {
|
|
119
|
+
this.scanStringRaw += character;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
this.scanInString = false;
|
|
123
|
+
this.rootLastSignificant = '"';
|
|
124
|
+
if (this.scanStringIsRootKey) {
|
|
125
|
+
try {
|
|
126
|
+
if (JSON.parse(`"${this.scanStringRaw}"`) === "answer") {
|
|
127
|
+
this.answerKeyState = "after_key";
|
|
63
128
|
}
|
|
64
129
|
}
|
|
65
|
-
|
|
66
|
-
this.
|
|
67
|
-
}
|
|
68
|
-
else if (ch === '"') {
|
|
69
|
-
this._complete = true;
|
|
70
|
-
return out; // stop processing, closing quote found
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
out += ch;
|
|
130
|
+
catch {
|
|
131
|
+
this.invalid = true;
|
|
74
132
|
}
|
|
75
133
|
}
|
|
134
|
+
this.scanStringRaw = "";
|
|
135
|
+
this.scanStringIsRootKey = false;
|
|
136
|
+
return;
|
|
76
137
|
}
|
|
77
|
-
|
|
138
|
+
if (this.answerKeyState === "after_key") {
|
|
139
|
+
if (isJsonWhitespace(character))
|
|
140
|
+
return;
|
|
141
|
+
if (character === ":") {
|
|
142
|
+
this.answerKeyState = "after_colon";
|
|
143
|
+
this.rootLastSignificant = character;
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
this.invalid = true;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (this.answerKeyState === "after_colon") {
|
|
150
|
+
if (isJsonWhitespace(character))
|
|
151
|
+
return;
|
|
152
|
+
this.answerKeyState = "none";
|
|
153
|
+
if (character === '"') {
|
|
154
|
+
this._active = true;
|
|
155
|
+
this.rootLastSignificant = "value";
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (isJsonWhitespace(character))
|
|
160
|
+
return;
|
|
161
|
+
const atRootObject = this.containers.length === 1 && this.containers[0] === "object";
|
|
162
|
+
if (character === '"') {
|
|
163
|
+
this.scanInString = true;
|
|
164
|
+
this.scanEscape = false;
|
|
165
|
+
this.scanStringRaw = "";
|
|
166
|
+
this.scanStringIsRootKey = atRootObject
|
|
167
|
+
&& (this.rootLastSignificant === "{" || this.rootLastSignificant === ",");
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (character === "{" || character === "[") {
|
|
171
|
+
if (atRootObject)
|
|
172
|
+
this.rootLastSignificant = "value";
|
|
173
|
+
this.containers.push(character === "{" ? "object" : "array");
|
|
174
|
+
if (this.containers.length === 1 && character === "{") {
|
|
175
|
+
this.rootLastSignificant = character;
|
|
176
|
+
}
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (character === "}" || character === "]") {
|
|
180
|
+
const expected = character === "}" ? "object" : "array";
|
|
181
|
+
if (this.containers.at(-1) !== expected) {
|
|
182
|
+
this.invalid = true;
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
this.containers.pop();
|
|
186
|
+
if (this.containers.length === 1 && this.containers[0] === "object") {
|
|
187
|
+
this.rootLastSignificant = "value";
|
|
188
|
+
}
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (atRootObject)
|
|
192
|
+
this.rootLastSignificant = character;
|
|
193
|
+
}
|
|
194
|
+
processAnswerCharacter(character) {
|
|
195
|
+
if (this.decodeState === "unicode") {
|
|
196
|
+
if (!/^[0-9A-Fa-f]$/.test(character)) {
|
|
197
|
+
this.invalid = true;
|
|
198
|
+
return "";
|
|
199
|
+
}
|
|
200
|
+
this.unicodeDigits += character;
|
|
201
|
+
if (this.unicodeDigits.length < 4)
|
|
202
|
+
return "";
|
|
203
|
+
const decoded = String.fromCharCode(Number.parseInt(this.unicodeDigits, 16));
|
|
204
|
+
this.unicodeDigits = "";
|
|
205
|
+
this.decodeState = "normal";
|
|
206
|
+
return this.appendDecodedUnit(decoded);
|
|
207
|
+
}
|
|
208
|
+
if (this.decodeState === "escape") {
|
|
209
|
+
this.decodeState = "normal";
|
|
210
|
+
switch (character) {
|
|
211
|
+
case '"': return this.appendDecodedUnit('"');
|
|
212
|
+
case "\\": return this.appendDecodedUnit("\\");
|
|
213
|
+
case "/": return this.appendDecodedUnit("/");
|
|
214
|
+
case "b": return this.appendDecodedUnit("\b");
|
|
215
|
+
case "f": return this.appendDecodedUnit("\f");
|
|
216
|
+
case "n": return this.appendDecodedUnit("\n");
|
|
217
|
+
case "r": return this.appendDecodedUnit("\r");
|
|
218
|
+
case "t": return this.appendDecodedUnit("\t");
|
|
219
|
+
case "u":
|
|
220
|
+
this.decodeState = "unicode";
|
|
221
|
+
return "";
|
|
222
|
+
default:
|
|
223
|
+
this.invalid = true;
|
|
224
|
+
return "";
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (character === "\\") {
|
|
228
|
+
this.decodeState = "escape";
|
|
229
|
+
return "";
|
|
230
|
+
}
|
|
231
|
+
if (character === '"') {
|
|
232
|
+
const trailing = this.flushPendingHighSurrogate();
|
|
233
|
+
this._complete = true;
|
|
234
|
+
return trailing;
|
|
235
|
+
}
|
|
236
|
+
if (character.charCodeAt(0) <= 0x1F) {
|
|
237
|
+
this.invalid = true;
|
|
238
|
+
return "";
|
|
239
|
+
}
|
|
240
|
+
return this.appendDecodedUnit(character);
|
|
241
|
+
}
|
|
242
|
+
appendDecodedUnit(character) {
|
|
243
|
+
let out = "";
|
|
244
|
+
if (this.pendingHighSurrogate) {
|
|
245
|
+
if (isLowSurrogate(character)) {
|
|
246
|
+
const pair = this.pendingHighSurrogate + character;
|
|
247
|
+
this.pendingHighSurrogate = "";
|
|
248
|
+
this.decodedAnswer += pair;
|
|
249
|
+
return pair;
|
|
250
|
+
}
|
|
251
|
+
out = this.flushPendingHighSurrogate();
|
|
252
|
+
}
|
|
253
|
+
if (isHighSurrogate(character)) {
|
|
254
|
+
this.pendingHighSurrogate = character;
|
|
255
|
+
return out;
|
|
256
|
+
}
|
|
257
|
+
this.decodedAnswer += character;
|
|
258
|
+
return out + character;
|
|
259
|
+
}
|
|
260
|
+
flushPendingHighSurrogate() {
|
|
261
|
+
if (!this.pendingHighSurrogate)
|
|
262
|
+
return "";
|
|
263
|
+
const pending = this.pendingHighSurrogate;
|
|
264
|
+
this.pendingHighSurrogate = "";
|
|
265
|
+
this.decodedAnswer += pending;
|
|
266
|
+
return pending;
|
|
267
|
+
}
|
|
268
|
+
isIncompleteAtEof(error) {
|
|
269
|
+
if (this._active && !this._complete)
|
|
270
|
+
return true;
|
|
271
|
+
if (this.rawArguments.length === 0)
|
|
272
|
+
return true;
|
|
273
|
+
if (!(error instanceof SyntaxError))
|
|
274
|
+
return false;
|
|
275
|
+
if (/Unexpected end|Unterminated string/i.test(error.message))
|
|
276
|
+
return true;
|
|
277
|
+
const position = /position (\d+)/i.exec(error.message);
|
|
278
|
+
return position ? Number(position[1]) >= this.rawArguments.length : false;
|
|
279
|
+
}
|
|
280
|
+
setTerminalFailure(errorCode) {
|
|
281
|
+
this.terminalResult = { ok: false, errorCode };
|
|
282
|
+
return this.terminalResult;
|
|
78
283
|
}
|
|
79
284
|
}
|
|
80
285
|
exports.SettleParser = SettleParser;
|
|
@@ -84,14 +289,23 @@ exports.SettleParser = SettleParser;
|
|
|
84
289
|
class SettleStreamer {
|
|
85
290
|
parser = new SettleParser();
|
|
86
291
|
_detected = false;
|
|
292
|
+
_streamed = false;
|
|
87
293
|
callbacks;
|
|
88
294
|
enabled;
|
|
295
|
+
mode;
|
|
296
|
+
observedArguments = "";
|
|
297
|
+
eagerAnswer = "";
|
|
298
|
+
terminalResult;
|
|
299
|
+
cancelled = false;
|
|
89
300
|
constructor(callbacks, enabled = true) {
|
|
90
301
|
this.callbacks = callbacks;
|
|
91
302
|
this.enabled = enabled;
|
|
303
|
+
// Production callback owners declare their mode explicitly. Retain the
|
|
304
|
+
// historical retractable default for third-party/in-test callback objects.
|
|
305
|
+
this.mode = callbacks.settleOutputMode ?? "retractable_buffer";
|
|
92
306
|
}
|
|
93
307
|
get detected() { return this._detected; }
|
|
94
|
-
get streamed() { return this.
|
|
308
|
+
get streamed() { return this._streamed; }
|
|
95
309
|
/** Mark settle as detected. Calls onClearText on the callbacks. */
|
|
96
310
|
activate() {
|
|
97
311
|
if (!this.enabled)
|
|
@@ -99,7 +313,8 @@ class SettleStreamer {
|
|
|
99
313
|
if (this._detected)
|
|
100
314
|
return;
|
|
101
315
|
this._detected = true;
|
|
102
|
-
this.
|
|
316
|
+
if (this.mode === "retractable_buffer")
|
|
317
|
+
this.callbacks.onClearText?.();
|
|
103
318
|
}
|
|
104
319
|
/** Feed an argument delta through the parser. Emits text via onTextChunk. */
|
|
105
320
|
processDelta(delta) {
|
|
@@ -107,12 +322,115 @@ class SettleStreamer {
|
|
|
107
322
|
return;
|
|
108
323
|
if (!this._detected)
|
|
109
324
|
return;
|
|
325
|
+
if (this.cancelled || this.terminalResult)
|
|
326
|
+
return;
|
|
327
|
+
this.observedArguments += delta;
|
|
110
328
|
const text = this.parser.process(delta);
|
|
111
|
-
if (text)
|
|
329
|
+
if (text && this.mode === "retractable_buffer") {
|
|
112
330
|
this.callbacks.onTextChunk(text);
|
|
331
|
+
this.eagerAnswer += text;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/** Validate the completed provider arguments exactly once, then commit or
|
|
335
|
+
* retract callback-visible output according to the callback owner's mode. */
|
|
336
|
+
finish(completedArguments) {
|
|
337
|
+
if (!this.enabled || !this._detected || this.cancelled)
|
|
338
|
+
return undefined;
|
|
339
|
+
if (this.terminalResult)
|
|
340
|
+
return this.terminalResult;
|
|
341
|
+
let finalSuffix = "";
|
|
342
|
+
let replacedObservedArguments = false;
|
|
343
|
+
if (completedArguments.startsWith(this.observedArguments)) {
|
|
344
|
+
const suffix = completedArguments.slice(this.observedArguments.length);
|
|
345
|
+
this.observedArguments = completedArguments;
|
|
346
|
+
finalSuffix = this.parser.process(suffix);
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
// A provider may normalize or replace its streamed argument fragments in
|
|
350
|
+
// the completed tool call. Re-parse that one authoritative final value;
|
|
351
|
+
// any eager output from the divergent fragment is retracted below.
|
|
352
|
+
replacedObservedArguments = true;
|
|
353
|
+
this.parser = new SettleParser();
|
|
354
|
+
this.observedArguments = completedArguments;
|
|
355
|
+
finalSuffix = this.parser.process(completedArguments);
|
|
356
|
+
}
|
|
357
|
+
const result = this.parser.finish();
|
|
358
|
+
// Cache before invoking callbacks: a callback failure must never make a
|
|
359
|
+
// repeated finish parse or commit the answer again.
|
|
360
|
+
this.terminalResult = result;
|
|
361
|
+
if (!result.ok) {
|
|
362
|
+
if (this.mode === "retractable_buffer")
|
|
363
|
+
this.callbacks.onClearText?.();
|
|
364
|
+
return result;
|
|
365
|
+
}
|
|
366
|
+
if (this.mode === "retractable_buffer") {
|
|
367
|
+
if (replacedObservedArguments) {
|
|
368
|
+
this.callbacks.onClearText?.();
|
|
369
|
+
this.callbacks.onTextChunk(result.answer);
|
|
370
|
+
}
|
|
371
|
+
else if (finalSuffix) {
|
|
372
|
+
this.callbacks.onTextChunk(finalSuffix);
|
|
373
|
+
}
|
|
374
|
+
this._streamed = true;
|
|
375
|
+
}
|
|
376
|
+
return result;
|
|
377
|
+
}
|
|
378
|
+
/** Cancel a partial provider stream without terminal parsing or commit. */
|
|
379
|
+
cancel() {
|
|
380
|
+
if (!this.enabled || !this._detected || this.cancelled || this.terminalResult)
|
|
381
|
+
return;
|
|
382
|
+
this.cancelled = true;
|
|
383
|
+
if (this.mode === "retractable_buffer" && this.eagerAnswer) {
|
|
384
|
+
this.callbacks.onClearText?.();
|
|
385
|
+
}
|
|
113
386
|
}
|
|
114
387
|
}
|
|
115
388
|
exports.SettleStreamer = SettleStreamer;
|
|
389
|
+
function finalizeSettleStream(streamer, completedArguments) {
|
|
390
|
+
try {
|
|
391
|
+
return streamer.finish(completedArguments);
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
throw new SettleFinalizationCallbackError(error);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/** Holds ordinary provider text until a final-only response is known not to be
|
|
398
|
+
* an invalid settle. Retractable callback owners continue receiving deltas. */
|
|
399
|
+
class ProviderTextOutput {
|
|
400
|
+
callbacks;
|
|
401
|
+
bufferedText = "";
|
|
402
|
+
deferred;
|
|
403
|
+
suppressed = false;
|
|
404
|
+
constructor(callbacks) {
|
|
405
|
+
this.callbacks = callbacks;
|
|
406
|
+
this.deferred = callbacks.settleOutputMode === "final_only";
|
|
407
|
+
}
|
|
408
|
+
emit(text) {
|
|
409
|
+
if (this.suppressed)
|
|
410
|
+
return;
|
|
411
|
+
if (this.deferred) {
|
|
412
|
+
this.bufferedText += text;
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
this.callbacks.onTextChunk(text);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
suppress() {
|
|
419
|
+
this.bufferedText = "";
|
|
420
|
+
this.suppressed = true;
|
|
421
|
+
}
|
|
422
|
+
discard() {
|
|
423
|
+
this.bufferedText = "";
|
|
424
|
+
}
|
|
425
|
+
flush() {
|
|
426
|
+
if (!this.bufferedText)
|
|
427
|
+
return;
|
|
428
|
+
const text = this.bufferedText;
|
|
429
|
+
this.bufferedText = "";
|
|
430
|
+
this.callbacks.onTextChunk(text);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
exports.ProviderTextOutput = ProviderTextOutput;
|
|
116
434
|
exports.RESPONSES_FUNCTION_CALL_OUTPUT_CAP = 200_000;
|
|
117
435
|
function truncateResponsesFunctionCallOutput(output, maxChars = exports.RESPONSES_FUNCTION_CALL_OUTPUT_CAP) {
|
|
118
436
|
if (output.length <= maxChars)
|
|
@@ -259,6 +577,7 @@ async function streamChatCompletion(client, createParams, callbacks, signal, eag
|
|
|
259
577
|
let streamStarted = false;
|
|
260
578
|
let usage;
|
|
261
579
|
const answerStreamer = new SettleStreamer(callbacks, eagerSettleStreaming);
|
|
580
|
+
const textOutput = new ProviderTextOutput(callbacks);
|
|
262
581
|
// State machine for parsing inline <think> tags (MiniMax pattern)
|
|
263
582
|
let contentBuf = "";
|
|
264
583
|
let inThinkTag = false;
|
|
@@ -301,7 +620,7 @@ async function streamChatCompletion(client, createParams, callbacks, signal, eag
|
|
|
301
620
|
if (start !== -1) {
|
|
302
621
|
const text = contentBuf.slice(0, start);
|
|
303
622
|
if (text)
|
|
304
|
-
|
|
623
|
+
textOutput.emit(text);
|
|
305
624
|
contentBuf = contentBuf.slice(start + OPEN_TAG.length);
|
|
306
625
|
inThinkTag = true;
|
|
307
626
|
}
|
|
@@ -316,92 +635,117 @@ async function streamChatCompletion(client, createParams, callbacks, signal, eag
|
|
|
316
635
|
if (retain > 0) {
|
|
317
636
|
const text = contentBuf.slice(0, -retain);
|
|
318
637
|
if (text)
|
|
319
|
-
|
|
638
|
+
textOutput.emit(text);
|
|
320
639
|
contentBuf = contentBuf.slice(-retain);
|
|
321
640
|
return;
|
|
322
641
|
}
|
|
323
642
|
}
|
|
324
643
|
// All content, flush it
|
|
325
|
-
|
|
644
|
+
textOutput.emit(contentBuf);
|
|
326
645
|
contentBuf = "";
|
|
327
646
|
}
|
|
328
647
|
}
|
|
329
648
|
}
|
|
330
649
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const d = chunk.choices[0]?.delta;
|
|
345
|
-
if (!d)
|
|
346
|
-
continue;
|
|
347
|
-
// Handle reasoning_content (Azure AI models like DeepSeek-R1)
|
|
348
|
-
if (d.reasoning_content) {
|
|
349
|
-
if (!streamStarted) {
|
|
350
|
-
callbacks.onModelStreamStart();
|
|
351
|
-
streamStarted = true;
|
|
650
|
+
try {
|
|
651
|
+
for await (const chunk of response) {
|
|
652
|
+
if (signal?.aborted)
|
|
653
|
+
break;
|
|
654
|
+
// Capture usage from final chunk (sent when stream_options.include_usage is true)
|
|
655
|
+
if (chunk.usage) {
|
|
656
|
+
const u = chunk.usage;
|
|
657
|
+
usage = {
|
|
658
|
+
input_tokens: u.prompt_tokens,
|
|
659
|
+
output_tokens: u.completion_tokens,
|
|
660
|
+
reasoning_tokens: u.completion_tokens_details?.reasoning_tokens ?? 0,
|
|
661
|
+
total_tokens: u.total_tokens,
|
|
662
|
+
};
|
|
352
663
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
streamStarted
|
|
664
|
+
const d = chunk.choices[0]?.delta;
|
|
665
|
+
if (!d)
|
|
666
|
+
continue;
|
|
667
|
+
// Handle reasoning_content (Azure AI models like DeepSeek-R1)
|
|
668
|
+
if (d.reasoning_content) {
|
|
669
|
+
if (!streamStarted) {
|
|
670
|
+
callbacks.onModelStreamStart();
|
|
671
|
+
streamStarted = true;
|
|
672
|
+
}
|
|
673
|
+
callbacks.onReasoningChunk(d.reasoning_content);
|
|
359
674
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
if (d.tool_calls) {
|
|
365
|
-
for (const tc of d.tool_calls) {
|
|
366
|
-
if (!toolCalls[tc.index])
|
|
367
|
-
toolCalls[tc.index] = {
|
|
368
|
-
id: tc.id ?? "",
|
|
369
|
-
name: tc.function?.name ?? "",
|
|
370
|
-
arguments: "",
|
|
371
|
-
};
|
|
372
|
-
if (tc.id)
|
|
373
|
-
toolCalls[tc.index].id = tc.id;
|
|
374
|
-
if (tc.function?.name) {
|
|
375
|
-
toolCalls[tc.index].name = tc.function.name;
|
|
376
|
-
// Detect settle tool call on first name delta.
|
|
377
|
-
// Only activate streaming if this is the sole tool call (index 0
|
|
378
|
-
// and no other indices seen). Mixed calls are rejected by core.ts.
|
|
379
|
-
if (tc.function.name === "settle" && !answerStreamer.detected
|
|
380
|
-
&& tc.index === 0 && Object.keys(toolCalls).length === 1) {
|
|
381
|
-
answerStreamer.activate();
|
|
382
|
-
}
|
|
675
|
+
if (d.content) {
|
|
676
|
+
if (!streamStarted) {
|
|
677
|
+
callbacks.onModelStreamStart();
|
|
678
|
+
streamStarted = true;
|
|
383
679
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
680
|
+
content += d.content;
|
|
681
|
+
contentBuf += d.content;
|
|
682
|
+
processContentBuf(false);
|
|
683
|
+
}
|
|
684
|
+
if (d.tool_calls) {
|
|
685
|
+
for (const tc of d.tool_calls) {
|
|
686
|
+
if (!toolCalls[tc.index])
|
|
687
|
+
toolCalls[tc.index] = {
|
|
688
|
+
id: tc.id ?? "",
|
|
689
|
+
name: tc.function?.name ?? "",
|
|
690
|
+
arguments: "",
|
|
691
|
+
};
|
|
692
|
+
if (tc.id)
|
|
693
|
+
toolCalls[tc.index].id = tc.id;
|
|
694
|
+
if (tc.function?.name) {
|
|
695
|
+
toolCalls[tc.index].name = tc.function.name;
|
|
696
|
+
// Detect settle tool call on first name delta.
|
|
697
|
+
// Only activate streaming if this is the sole tool call (index 0
|
|
698
|
+
// and no other indices seen). Mixed calls are rejected by core.ts.
|
|
699
|
+
if (tc.function.name === "settle" && !answerStreamer.detected
|
|
700
|
+
&& tc.index === 0 && Object.keys(toolCalls).length === 1) {
|
|
701
|
+
answerStreamer.activate();
|
|
702
|
+
textOutput.suppress();
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
if (tc.function?.arguments) {
|
|
706
|
+
toolCalls[tc.index].arguments += tc.function.arguments;
|
|
707
|
+
// Feed settle argument deltas to the parser for progressive
|
|
708
|
+
// streaming, but only when it appears to be the sole tool call.
|
|
709
|
+
if (answerStreamer.detected && toolCalls[tc.index].name === "settle"
|
|
710
|
+
&& Object.keys(toolCalls).length === 1) {
|
|
711
|
+
answerStreamer.processDelta(tc.function.arguments);
|
|
712
|
+
}
|
|
391
713
|
}
|
|
392
714
|
}
|
|
393
715
|
}
|
|
394
716
|
}
|
|
395
717
|
}
|
|
718
|
+
catch (error) {
|
|
719
|
+
textOutput.discard();
|
|
720
|
+
answerStreamer.cancel();
|
|
721
|
+
throw error;
|
|
722
|
+
}
|
|
396
723
|
// Flush any remaining buffer at end of stream
|
|
397
724
|
if (contentBuf)
|
|
398
725
|
processContentBuf(true);
|
|
726
|
+
const completedToolCalls = Object.values(toolCalls);
|
|
727
|
+
let settleFinalization;
|
|
728
|
+
if (signal?.aborted) {
|
|
729
|
+
textOutput.discard();
|
|
730
|
+
answerStreamer.cancel();
|
|
731
|
+
}
|
|
732
|
+
else if (completedToolCalls.length === 1
|
|
733
|
+
&& completedToolCalls[0].name === "settle"
|
|
734
|
+
&& answerStreamer.detected) {
|
|
735
|
+
textOutput.discard();
|
|
736
|
+
settleFinalization = finalizeSettleStream(answerStreamer, completedToolCalls[0].arguments);
|
|
737
|
+
}
|
|
738
|
+
else {
|
|
739
|
+
answerStreamer.cancel();
|
|
740
|
+
textOutput.flush();
|
|
741
|
+
}
|
|
399
742
|
return {
|
|
400
743
|
content,
|
|
401
|
-
toolCalls:
|
|
744
|
+
toolCalls: completedToolCalls,
|
|
402
745
|
outputItems: [],
|
|
403
746
|
usage,
|
|
404
747
|
settleStreamed: answerStreamer.streamed,
|
|
748
|
+
...(settleFinalization ? { settleFinalization } : {}),
|
|
405
749
|
};
|
|
406
750
|
}
|
|
407
751
|
async function streamResponsesApi(client, createParams, callbacks, signal, eagerSettleStreaming = true) {
|
|
@@ -420,84 +764,119 @@ async function streamResponsesApi(client, createParams, callbacks, signal, eager
|
|
|
420
764
|
let currentToolCall = null;
|
|
421
765
|
let usage;
|
|
422
766
|
const answerStreamer = new SettleStreamer(callbacks, eagerSettleStreaming);
|
|
767
|
+
const textOutput = new ProviderTextOutput(callbacks);
|
|
423
768
|
let functionCallCount = 0;
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
switch (event.type) {
|
|
428
|
-
case "response.output_text.delta":
|
|
429
|
-
case "response.reasoning_summary_text.delta": {
|
|
430
|
-
if (!streamStarted) {
|
|
431
|
-
callbacks.onModelStreamStart();
|
|
432
|
-
streamStarted = true;
|
|
433
|
-
}
|
|
434
|
-
const delta = String(event.delta);
|
|
435
|
-
if (event.type === "response.output_text.delta") {
|
|
436
|
-
callbacks.onTextChunk(delta);
|
|
437
|
-
content += delta;
|
|
438
|
-
}
|
|
439
|
-
else {
|
|
440
|
-
callbacks.onReasoningChunk(delta);
|
|
441
|
-
}
|
|
769
|
+
try {
|
|
770
|
+
for await (const event of response) {
|
|
771
|
+
if (signal?.aborted)
|
|
442
772
|
break;
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
// Mixed calls are rejected by core.ts; no need to stream their args.
|
|
455
|
-
if (String(event.item.name) === "settle" && functionCallCount === 1) {
|
|
456
|
-
answerStreamer.activate();
|
|
773
|
+
switch (event.type) {
|
|
774
|
+
case "response.output_text.delta":
|
|
775
|
+
case "response.reasoning_summary_text.delta": {
|
|
776
|
+
if (!streamStarted) {
|
|
777
|
+
callbacks.onModelStreamStart();
|
|
778
|
+
streamStarted = true;
|
|
779
|
+
}
|
|
780
|
+
const delta = String(event.delta);
|
|
781
|
+
if (event.type === "response.output_text.delta") {
|
|
782
|
+
textOutput.emit(delta);
|
|
783
|
+
content += delta;
|
|
457
784
|
}
|
|
785
|
+
else {
|
|
786
|
+
callbacks.onReasoningChunk(delta);
|
|
787
|
+
}
|
|
788
|
+
break;
|
|
458
789
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
790
|
+
case "response.output_item.added": {
|
|
791
|
+
if (event.item?.type === "function_call") {
|
|
792
|
+
functionCallCount++;
|
|
793
|
+
currentToolCall = {
|
|
794
|
+
call_id: String(event.item.call_id),
|
|
795
|
+
name: String(event.item.name),
|
|
796
|
+
arguments: "",
|
|
797
|
+
};
|
|
798
|
+
// Detect settle function call -- clear any streamed noise.
|
|
799
|
+
// Only activate when this is the first (and so far only) function call.
|
|
800
|
+
// Mixed calls are rejected by core.ts; no need to stream their args.
|
|
801
|
+
if (String(event.item.name) === "settle" && functionCallCount === 1) {
|
|
802
|
+
answerStreamer.activate();
|
|
803
|
+
textOutput.suppress();
|
|
804
|
+
}
|
|
469
805
|
}
|
|
806
|
+
break;
|
|
470
807
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
}
|
|
481
|
-
|
|
808
|
+
case "response.function_call_arguments.delta": {
|
|
809
|
+
if (currentToolCall) {
|
|
810
|
+
currentToolCall.arguments += event.delta;
|
|
811
|
+
// Feed settle argument deltas to the parser for progressive
|
|
812
|
+
// streaming, but only when it appears to be the sole function call.
|
|
813
|
+
if (answerStreamer.detected && currentToolCall.name === "settle"
|
|
814
|
+
&& functionCallCount === 1) {
|
|
815
|
+
answerStreamer.processDelta(String(event.delta));
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
break;
|
|
482
819
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
};
|
|
820
|
+
case "response.output_item.done": {
|
|
821
|
+
outputItems.push(event.item);
|
|
822
|
+
if (event.item?.type === "function_call") {
|
|
823
|
+
toolCalls.push({
|
|
824
|
+
id: String(event.item.call_id),
|
|
825
|
+
name: String(event.item.name),
|
|
826
|
+
arguments: String(event.item.arguments),
|
|
827
|
+
});
|
|
828
|
+
currentToolCall = null;
|
|
829
|
+
}
|
|
830
|
+
break;
|
|
495
831
|
}
|
|
496
|
-
|
|
832
|
+
case "response.completed":
|
|
833
|
+
case "response.done": {
|
|
834
|
+
const u = event.response?.usage;
|
|
835
|
+
if (u) {
|
|
836
|
+
usage = {
|
|
837
|
+
input_tokens: u.input_tokens,
|
|
838
|
+
output_tokens: u.output_tokens,
|
|
839
|
+
reasoning_tokens: u.output_tokens_details?.reasoning_tokens ?? 0,
|
|
840
|
+
total_tokens: u.total_tokens,
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
break;
|
|
844
|
+
}
|
|
845
|
+
default:
|
|
846
|
+
// Unknown/unhandled events silently ignored
|
|
847
|
+
break;
|
|
497
848
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
catch (error) {
|
|
852
|
+
textOutput.discard();
|
|
853
|
+
answerStreamer.cancel();
|
|
854
|
+
throw error;
|
|
855
|
+
}
|
|
856
|
+
let settleFinalization;
|
|
857
|
+
if (signal?.aborted) {
|
|
858
|
+
textOutput.discard();
|
|
859
|
+
answerStreamer.cancel();
|
|
860
|
+
}
|
|
861
|
+
else {
|
|
862
|
+
if (toolCalls.length === 0 && functionCallCount === 1 && currentToolCall?.name === "settle") {
|
|
863
|
+
toolCalls.push({
|
|
864
|
+
id: currentToolCall.call_id,
|
|
865
|
+
name: currentToolCall.name,
|
|
866
|
+
arguments: currentToolCall.arguments,
|
|
867
|
+
});
|
|
868
|
+
currentToolCall = null;
|
|
869
|
+
}
|
|
870
|
+
const completedSettle = toolCalls.length === 1 && toolCalls[0].name === "settle"
|
|
871
|
+
? toolCalls[0]
|
|
872
|
+
: undefined;
|
|
873
|
+
if (completedSettle && answerStreamer.detected) {
|
|
874
|
+
textOutput.discard();
|
|
875
|
+
settleFinalization = finalizeSettleStream(answerStreamer, completedSettle.arguments);
|
|
876
|
+
}
|
|
877
|
+
else {
|
|
878
|
+
answerStreamer.cancel();
|
|
879
|
+
textOutput.flush();
|
|
501
880
|
}
|
|
502
881
|
}
|
|
503
882
|
return {
|
|
@@ -506,5 +885,6 @@ async function streamResponsesApi(client, createParams, callbacks, signal, eager
|
|
|
506
885
|
outputItems,
|
|
507
886
|
usage,
|
|
508
887
|
settleStreamed: answerStreamer.streamed,
|
|
888
|
+
...(settleFinalization ? { settleFinalization } : {}),
|
|
509
889
|
};
|
|
510
890
|
}
|