@protolabsai/proto 0.51.2 → 0.52.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.
Files changed (2) hide show
  1. package/cli.js +2263 -2085
  2. package/package.json +2 -2
package/cli.js CHANGED
@@ -168684,7 +168684,7 @@ __export(geminiContentGenerator_exports, {
168684
168684
  createGeminiContentGenerator: () => createGeminiContentGenerator
168685
168685
  });
168686
168686
  function createGeminiContentGenerator(config2, gcConfig) {
168687
- const version2 = "0.51.2";
168687
+ const version2 = "0.52.0";
168688
168688
  const userAgent2 = config2.userAgent || `QwenCode/${version2} (${process.platform}; ${process.arch})`;
168689
168689
  const baseHeaders = {
168690
168690
  "User-Agent": userAgent2
@@ -168957,382 +168957,434 @@ var init_rateLimit = __esm({
168957
168957
  }
168958
168958
  });
168959
168959
 
168960
- // node_modules/async-mutex/index.mjs
168961
- function insertSorted(a2, v2) {
168962
- const i4 = findIndexFromEnd(a2, (other2) => v2.priority <= other2.priority);
168963
- a2.splice(i4 + 1, 0, v2);
168964
- }
168965
- function findIndexFromEnd(a2, predicate) {
168966
- for (let i4 = a2.length - 1; i4 >= 0; i4--) {
168967
- if (predicate(a2[i4])) {
168968
- return i4;
168969
- }
168970
- }
168971
- return -1;
168960
+ // packages/core/dist/src/utils/contextLengthError.js
168961
+ function parseInteger(value) {
168962
+ return Number.parseInt(value.replace(/,/g, ""), 10);
168972
168963
  }
168973
- var E_TIMEOUT, E_ALREADY_LOCKED, E_CANCELED, __awaiter$2, Semaphore, __awaiter$1, Mutex;
168974
- var init_async_mutex = __esm({
168975
- "node_modules/async-mutex/index.mjs"() {
168976
- init_esbuild_shims();
168977
- E_TIMEOUT = new Error("timeout while waiting for mutex to become available");
168978
- E_ALREADY_LOCKED = new Error("mutex already locked");
168979
- E_CANCELED = new Error("request for lock canceled");
168980
- __awaiter$2 = function(thisArg, _arguments, P2, generator) {
168981
- function adopt(value) {
168982
- return value instanceof P2 ? value : new P2(function(resolve37) {
168983
- resolve37(value);
168984
- });
168985
- }
168986
- __name(adopt, "adopt");
168987
- return new (P2 || (P2 = Promise))(function(resolve37, reject) {
168988
- function fulfilled(value) {
168989
- try {
168990
- step(generator.next(value));
168991
- } catch (e4) {
168992
- reject(e4);
168993
- }
168994
- }
168995
- __name(fulfilled, "fulfilled");
168996
- function rejected(value) {
168997
- try {
168998
- step(generator["throw"](value));
168999
- } catch (e4) {
169000
- reject(e4);
169001
- }
169002
- }
169003
- __name(rejected, "rejected");
169004
- function step(result) {
169005
- result.done ? resolve37(result.value) : adopt(result.value).then(fulfilled, rejected);
169006
- }
169007
- __name(step, "step");
169008
- step((generator = generator.apply(thisArg, _arguments || [])).next());
169009
- });
169010
- };
169011
- Semaphore = class {
169012
- static {
169013
- __name(this, "Semaphore");
169014
- }
169015
- constructor(_value, _cancelError = E_CANCELED) {
169016
- this._value = _value;
169017
- this._cancelError = _cancelError;
169018
- this._queue = [];
169019
- this._weightedWaiters = [];
169020
- }
169021
- acquire(weight = 1, priority = 0) {
169022
- if (weight <= 0)
169023
- throw new Error(`invalid weight ${weight}: must be positive`);
169024
- return new Promise((resolve37, reject) => {
169025
- const task = { resolve: resolve37, reject, weight, priority };
169026
- const i4 = findIndexFromEnd(this._queue, (other2) => priority <= other2.priority);
169027
- if (i4 === -1 && weight <= this._value) {
169028
- this._dispatchItem(task);
169029
- } else {
169030
- this._queue.splice(i4 + 1, 0, task);
169031
- }
169032
- });
169033
- }
169034
- runExclusive(callback_1) {
169035
- return __awaiter$2(this, arguments, void 0, function* (callback, weight = 1, priority = 0) {
169036
- const [value, release2] = yield this.acquire(weight, priority);
169037
- try {
169038
- return yield callback(value);
169039
- } finally {
169040
- release2();
169041
- }
169042
- });
169043
- }
169044
- waitForUnlock(weight = 1, priority = 0) {
169045
- if (weight <= 0)
169046
- throw new Error(`invalid weight ${weight}: must be positive`);
169047
- if (this._couldLockImmediately(weight, priority)) {
169048
- return Promise.resolve();
169049
- } else {
169050
- return new Promise((resolve37) => {
169051
- if (!this._weightedWaiters[weight - 1])
169052
- this._weightedWaiters[weight - 1] = [];
169053
- insertSorted(this._weightedWaiters[weight - 1], { resolve: resolve37, priority });
169054
- });
169055
- }
169056
- }
169057
- isLocked() {
169058
- return this._value <= 0;
169059
- }
169060
- getValue() {
169061
- return this._value;
169062
- }
169063
- setValue(value) {
169064
- this._value = value;
169065
- this._dispatchQueue();
169066
- }
169067
- release(weight = 1) {
169068
- if (weight <= 0)
169069
- throw new Error(`invalid weight ${weight}: must be positive`);
169070
- this._value += weight;
169071
- this._dispatchQueue();
169072
- }
169073
- cancel() {
169074
- this._queue.forEach((entry) => entry.reject(this._cancelError));
169075
- this._queue = [];
169076
- }
169077
- _dispatchQueue() {
169078
- this._drainUnlockWaiters();
169079
- while (this._queue.length > 0 && this._queue[0].weight <= this._value) {
169080
- this._dispatchItem(this._queue.shift());
169081
- this._drainUnlockWaiters();
169082
- }
169083
- }
169084
- _dispatchItem(item) {
169085
- const previousValue = this._value;
169086
- this._value -= item.weight;
169087
- item.resolve([previousValue, this._newReleaser(item.weight)]);
169088
- }
169089
- _newReleaser(weight) {
169090
- let called = false;
169091
- return () => {
169092
- if (called)
169093
- return;
169094
- called = true;
169095
- this.release(weight);
169096
- };
169097
- }
169098
- _drainUnlockWaiters() {
169099
- if (this._queue.length === 0) {
169100
- for (let weight = this._value; weight > 0; weight--) {
169101
- const waiters = this._weightedWaiters[weight - 1];
169102
- if (!waiters)
169103
- continue;
169104
- waiters.forEach((waiter) => waiter.resolve());
169105
- this._weightedWaiters[weight - 1] = [];
169106
- }
169107
- } else {
169108
- const queuedPriority = this._queue[0].priority;
169109
- for (let weight = this._value; weight > 0; weight--) {
169110
- const waiters = this._weightedWaiters[weight - 1];
169111
- if (!waiters)
169112
- continue;
169113
- const i4 = waiters.findIndex((waiter) => waiter.priority <= queuedPriority);
169114
- (i4 === -1 ? waiters : waiters.splice(0, i4)).forEach((waiter) => waiter.resolve());
169115
- }
169116
- }
169117
- }
169118
- _couldLockImmediately(weight, priority) {
169119
- return (this._queue.length === 0 || this._queue[0].priority < priority) && weight <= this._value;
169120
- }
168964
+ function parseTokenCounts(text) {
168965
+ const greaterThanMatch = text.match(/(\d[\d,]*)\s*tokens?\s*>\s*(\d[\d,]*)/i);
168966
+ if (greaterThanMatch) {
168967
+ return {
168968
+ actualTokens: parseInteger(greaterThanMatch[1]),
168969
+ limitTokens: parseInteger(greaterThanMatch[2])
169121
168970
  };
169122
- __name(insertSorted, "insertSorted");
169123
- __name(findIndexFromEnd, "findIndexFromEnd");
169124
- __awaiter$1 = function(thisArg, _arguments, P2, generator) {
169125
- function adopt(value) {
169126
- return value instanceof P2 ? value : new P2(function(resolve37) {
169127
- resolve37(value);
169128
- });
169129
- }
169130
- __name(adopt, "adopt");
169131
- return new (P2 || (P2 = Promise))(function(resolve37, reject) {
169132
- function fulfilled(value) {
169133
- try {
169134
- step(generator.next(value));
169135
- } catch (e4) {
169136
- reject(e4);
169137
- }
169138
- }
169139
- __name(fulfilled, "fulfilled");
169140
- function rejected(value) {
169141
- try {
169142
- step(generator["throw"](value));
169143
- } catch (e4) {
169144
- reject(e4);
169145
- }
169146
- }
169147
- __name(rejected, "rejected");
169148
- function step(result) {
169149
- result.done ? resolve37(result.value) : adopt(result.value).then(fulfilled, rejected);
169150
- }
169151
- __name(step, "step");
169152
- step((generator = generator.apply(thisArg, _arguments || [])).next());
169153
- });
168971
+ }
168972
+ const openAiMatch = text.match(/maximum context length is\s*(\d[\d,]*)\s*tokens?[\s\S]*?(?:resulted in|requested|used)\s*(\d[\d,]*)\s*tokens?/i);
168973
+ if (openAiMatch) {
168974
+ return {
168975
+ actualTokens: parseInteger(openAiMatch[2]),
168976
+ limitTokens: parseInteger(openAiMatch[1])
169154
168977
  };
169155
- Mutex = class {
169156
- static {
169157
- __name(this, "Mutex");
169158
- }
169159
- constructor(cancelError) {
169160
- this._semaphore = new Semaphore(1, cancelError);
169161
- }
169162
- acquire() {
169163
- return __awaiter$1(this, arguments, void 0, function* (priority = 0) {
169164
- const [, releaser] = yield this._semaphore.acquire(1, priority);
169165
- return releaser;
169166
- });
169167
- }
169168
- runExclusive(callback, priority = 0) {
169169
- return this._semaphore.runExclusive(() => callback(), 1, priority);
169170
- }
169171
- isLocked() {
169172
- return this._semaphore.isLocked();
169173
- }
169174
- waitForUnlock(priority = 0) {
169175
- return this._semaphore.waitForUnlock(1, priority);
169176
- }
169177
- release() {
169178
- if (this._semaphore.isLocked())
169179
- this._semaphore.release();
169180
- }
169181
- cancel() {
169182
- return this._semaphore.cancel();
169183
- }
168978
+ }
168979
+ const maxContextLimitMatch = text.match(/maximum context length is\s*(\d[\d,]*)\s*tokens?/i);
168980
+ if (maxContextLimitMatch) {
168981
+ return {
168982
+ limitTokens: parseInteger(maxContextLimitMatch[1])
169184
168983
  };
169185
168984
  }
169186
- });
169187
-
169188
- // packages/core/dist/src/utils/jsonl-utils.js
169189
- import fs17 from "node:fs";
169190
- import path16 from "node:path";
169191
- import readline from "node:readline";
169192
- function getFileLock(filePath) {
169193
- if (!fileLocks.has(filePath)) {
169194
- fileLocks.set(filePath, new Mutex());
168985
+ const inputExceedsMatch = text.match(/input\s+token\s+(?:count|length)[^\d]*(\d[\d,]*)[\s\S]*?exceed(?:s|ed)?[\s\S]*?(?:maximum|limit)[^\d]*(\d[\d,]*)/i);
168986
+ if (inputExceedsMatch) {
168987
+ return {
168988
+ actualTokens: parseInteger(inputExceedsMatch[1]),
168989
+ limitTokens: parseInteger(inputExceedsMatch[2])
168990
+ };
169195
168991
  }
169196
- return fileLocks.get(filePath);
168992
+ return {};
169197
168993
  }
169198
- async function readLines(filePath, count) {
169199
- try {
169200
- const fileStream = fs17.createReadStream(filePath);
169201
- const rl = readline.createInterface({
169202
- input: fileStream,
169203
- crlfDelay: Infinity
169204
- });
169205
- const results = [];
169206
- for await (const line of rl) {
169207
- if (results.length >= count)
169208
- break;
169209
- const trimmed2 = line.trim();
169210
- if (trimmed2.length > 0) {
169211
- results.push(JSON.parse(trimmed2));
169212
- }
169213
- }
169214
- return results;
169215
- } catch (error40) {
169216
- if (error40.code !== "ENOENT") {
169217
- debugLogger21.error(`Error reading first ${count} lines from ${filePath}:`, error40);
168994
+ function tryParseEmbeddedJson(text) {
168995
+ const trimmed2 = text.trim();
168996
+ if (trimmed2.startsWith("{") || trimmed2.startsWith("[")) {
168997
+ try {
168998
+ return JSON.parse(trimmed2);
168999
+ } catch {
169218
169000
  }
169219
- return [];
169220
169001
  }
169221
- }
169222
- async function read(filePath) {
169002
+ const start2 = text.indexOf("{");
169003
+ const end = text.lastIndexOf("}");
169004
+ if (start2 === -1 || end <= start2) {
169005
+ return void 0;
169006
+ }
169223
169007
  try {
169224
- const fileStream = fs17.createReadStream(filePath);
169225
- const rl = readline.createInterface({
169226
- input: fileStream,
169227
- crlfDelay: Infinity
169228
- });
169229
- const results = [];
169230
- for await (const line of rl) {
169231
- const trimmed2 = line.trim();
169232
- if (trimmed2.length > 0) {
169233
- results.push(JSON.parse(trimmed2));
169234
- }
169235
- }
169236
- return results;
169237
- } catch (error40) {
169238
- if (error40.code !== "ENOENT") {
169239
- debugLogger21.error(`Error reading ${filePath}:`, error40);
169008
+ return JSON.parse(text.slice(start2, end + 1));
169009
+ } catch {
169010
+ return void 0;
169011
+ }
169012
+ }
169013
+ function collectStrings(value, seen, depth = 0) {
169014
+ if (depth > MAX_COLLECT_DEPTH || value === null || value === void 0) {
169015
+ return [];
169016
+ }
169017
+ if (typeof value === "string") {
169018
+ const parsed = tryParseEmbeddedJson(value);
169019
+ if (parsed === void 0) {
169020
+ return [value];
169240
169021
  }
169022
+ return [value, ...collectStrings(parsed, seen, depth + 1)];
169023
+ }
169024
+ if (typeof value === "number" || typeof value === "boolean") {
169025
+ return [String(value)];
169026
+ }
169027
+ if (typeof value !== "object") {
169028
+ return [];
169029
+ }
169030
+ if (seen.has(value)) {
169241
169031
  return [];
169242
169032
  }
169033
+ seen.add(value);
169034
+ const strings = [];
169035
+ if (value instanceof Error) {
169036
+ strings.push(value.name, value.message);
169037
+ strings.push(...collectStrings(value.cause, seen, depth + 1));
169038
+ }
169039
+ for (const [, nested] of Object.entries(value)) {
169040
+ strings.push(...collectStrings(nested, seen, depth + 1));
169041
+ }
169042
+ return strings;
169243
169043
  }
169244
- async function writeLine(filePath, data) {
169245
- const lock = getFileLock(filePath);
169246
- await lock.runExclusive(() => {
169247
- const line = `${JSON.stringify(data)}
169248
- `;
169249
- const dir = path16.dirname(filePath);
169250
- if (!fs17.existsSync(dir)) {
169251
- fs17.mkdirSync(dir, { recursive: true });
169044
+ function uniqueNonEmpty(values) {
169045
+ const seen = /* @__PURE__ */ new Set();
169046
+ const result = [];
169047
+ for (const value of values) {
169048
+ const trimmed2 = value.trim();
169049
+ if (!trimmed2 || seen.has(trimmed2)) {
169050
+ continue;
169252
169051
  }
169253
- fs17.appendFileSync(filePath, line, "utf8");
169254
- });
169052
+ seen.add(trimmed2);
169053
+ result.push(trimmed2);
169054
+ }
169055
+ return result;
169255
169056
  }
169256
- function writeLineSync(filePath, data) {
169257
- const line = `${JSON.stringify(data)}
169258
- `;
169259
- const dir = path16.dirname(filePath);
169260
- if (!fs17.existsSync(dir)) {
169261
- fs17.mkdirSync(dir, { recursive: true });
169057
+ function getContextLengthExceededInfo(error40) {
169058
+ const fragments = uniqueNonEmpty(collectStrings(error40, /* @__PURE__ */ new Set()));
169059
+ const message = fragments.join("\n");
169060
+ const isTimeout = TIMEOUT_PATTERNS.some((pattern) => pattern.test(message));
169061
+ const isExceeded = !isTimeout && fragments.some((fragment) => CONTEXT_LENGTH_PATTERNS.some((pattern) => pattern.test(fragment)));
169062
+ const counts = isExceeded ? parseTokenCounts(message) : {};
169063
+ return {
169064
+ isExceeded,
169065
+ message,
169066
+ ...counts
169067
+ };
169068
+ }
169069
+ var MAX_COLLECT_DEPTH, TIMEOUT_PATTERNS, CONTEXT_LENGTH_PATTERNS;
169070
+ var init_contextLengthError = __esm({
169071
+ "packages/core/dist/src/utils/contextLengthError.js"() {
169072
+ "use strict";
169073
+ init_esbuild_shims();
169074
+ MAX_COLLECT_DEPTH = 4;
169075
+ TIMEOUT_PATTERNS = [
169076
+ /\bcontext deadline exceeded\b/i,
169077
+ /\bdeadline exceeded\b/i,
169078
+ /\b(?:request|connection|read|context)\s+timed out\b/i,
169079
+ /\b(?:request|connection|read|context)\s+timeout\b/i,
169080
+ /\b(?:timeout|timed out)\s+(?:after|while|during)\b/i
169081
+ ];
169082
+ CONTEXT_LENGTH_PATTERNS = [
169083
+ /\bcontext[_\s-]?length[_\s-]?exceeded\b/i,
169084
+ /\bmaximum context length\b/i,
169085
+ /\bprompt\s+(?:is\s+)?too long\b/i,
169086
+ /\binput\s+(?:token\s+)?(?:count\s+|length\s+)?(?:is\s+)?too long\b/i,
169087
+ /\brange of input length should be\b/i,
169088
+ /\btoo many tokens\b/i,
169089
+ /\btokens?\s*>\s*[\d,]+\s*(?:maximum|max|limit)\b/i,
169090
+ /\b(?:input|prompt|messages?|context)\b[^\n]{0,120}\btokens?\b[^\n]{0,120}\bexceed(?:s|ed|ing)?\b/i
169091
+ ];
169092
+ __name(parseInteger, "parseInteger");
169093
+ __name(parseTokenCounts, "parseTokenCounts");
169094
+ __name(tryParseEmbeddedJson, "tryParseEmbeddedJson");
169095
+ __name(collectStrings, "collectStrings");
169096
+ __name(uniqueNonEmpty, "uniqueNonEmpty");
169097
+ __name(getContextLengthExceededInfo, "getContextLengthExceededInfo");
169098
+ }
169099
+ });
169100
+
169101
+ // packages/core/dist/src/utils/thoughtUtils.js
169102
+ function parseThought(rawText) {
169103
+ const startIndex = rawText.indexOf(START_DELIMITER);
169104
+ if (startIndex === -1) {
169105
+ return { subject: "", description: rawText };
169106
+ }
169107
+ const endIndex = rawText.indexOf(END_DELIMITER, startIndex + START_DELIMITER.length);
169108
+ if (endIndex === -1) {
169109
+ return { subject: "", description: rawText };
169262
169110
  }
169263
- fs17.appendFileSync(filePath, line, "utf8");
169111
+ const subject = rawText.substring(startIndex + START_DELIMITER.length, endIndex).trim();
169112
+ const description = (rawText.substring(0, startIndex) + rawText.substring(endIndex + END_DELIMITER.length)).trim();
169113
+ return { subject, description };
169264
169114
  }
169265
- function write(filePath, data) {
169266
- const lines = data.map((item) => JSON.stringify(item)).join("\n");
169267
- const dir = path16.dirname(filePath);
169268
- if (!fs17.existsSync(dir)) {
169269
- fs17.mkdirSync(dir, { recursive: true });
169115
+ function getThoughtText(response) {
169116
+ if (response.candidates && response.candidates.length > 0) {
169117
+ const candidate = response.candidates[0];
169118
+ if (candidate.content && candidate.content.parts && candidate.content.parts.length > 0) {
169119
+ return candidate.content.parts.filter((part) => part.thought && !isInternalPart(part)).map((part) => part.text ?? "").join("");
169120
+ }
169270
169121
  }
169271
- fs17.writeFileSync(filePath, `${lines}
169272
- `, "utf8");
169122
+ return null;
169273
169123
  }
169274
- async function countLines(filePath) {
169275
- try {
169276
- const fileStream = fs17.createReadStream(filePath);
169277
- const rl = readline.createInterface({
169278
- input: fileStream,
169279
- crlfDelay: Infinity
169124
+ var START_DELIMITER, END_DELIMITER;
169125
+ var init_thoughtUtils = __esm({
169126
+ "packages/core/dist/src/utils/thoughtUtils.js"() {
169127
+ "use strict";
169128
+ init_esbuild_shims();
169129
+ init_partUtils();
169130
+ START_DELIMITER = "**";
169131
+ END_DELIMITER = "**";
169132
+ __name(parseThought, "parseThought");
169133
+ __name(getThoughtText, "getThoughtText");
169134
+ }
169135
+ });
169136
+
169137
+ // packages/core/dist/src/utils/streamStall.js
169138
+ async function* withChunkTimeout(source2, timeoutMs) {
169139
+ while (true) {
169140
+ let timer;
169141
+ const stallPromise = new Promise((_2, reject) => {
169142
+ timer = setTimeout(() => reject(new StreamStallError(timeoutMs)), timeoutMs);
169280
169143
  });
169281
- let count = 0;
169282
- for await (const line of rl) {
169283
- if (line.trim().length > 0) {
169284
- count++;
169144
+ let result;
169145
+ try {
169146
+ result = await Promise.race([source2.next(), stallPromise]);
169147
+ clearTimeout(timer);
169148
+ } catch (err3) {
169149
+ clearTimeout(timer);
169150
+ try {
169151
+ void source2.return?.(void 0);
169152
+ } catch {
169285
169153
  }
169154
+ throw err3;
169286
169155
  }
169287
- return count;
169288
- } catch (error40) {
169289
- if (error40.code !== "ENOENT") {
169290
- debugLogger21.error(`Error counting lines in ${filePath}:`, error40);
169291
- }
169292
- return 0;
169156
+ if (result.done)
169157
+ return;
169158
+ yield result.value;
169293
169159
  }
169294
169160
  }
169295
- function exists(filePath) {
169296
- try {
169297
- const stats = fs17.statSync(filePath);
169298
- return stats.isFile() && stats.size > 0;
169299
- } catch {
169300
- return false;
169161
+ var StreamStallError;
169162
+ var init_streamStall = __esm({
169163
+ "packages/core/dist/src/utils/streamStall.js"() {
169164
+ "use strict";
169165
+ init_esbuild_shims();
169166
+ StreamStallError = class extends Error {
169167
+ static {
169168
+ __name(this, "StreamStallError");
169169
+ }
169170
+ timeoutMs;
169171
+ constructor(timeoutMs) {
169172
+ super(`Stream stalled: no data received for ${timeoutMs / 1e3}s. The model connection may have dropped \u2014 please try again.`);
169173
+ this.name = "StreamStallError";
169174
+ this.timeoutMs = timeoutMs;
169175
+ }
169176
+ };
169177
+ __name(withChunkTimeout, "withChunkTimeout");
169301
169178
  }
169179
+ });
169180
+
169181
+ // packages/core/dist/src/core/turn.js
169182
+ function getCitations(resp) {
169183
+ return (resp.candidates?.[0]?.citationMetadata?.citations ?? []).filter((citation) => citation.uri !== void 0).map((citation) => {
169184
+ if (citation.title) {
169185
+ return `(${citation.title}) ${citation.uri}`;
169186
+ }
169187
+ return citation.uri;
169188
+ });
169302
169189
  }
169303
- var debugLogger21, fileLocks;
169304
- var init_jsonl_utils = __esm({
169305
- "packages/core/dist/src/utils/jsonl-utils.js"() {
169190
+ var STREAM_STALL_TIMEOUT_MS, GeminiEventType, CompressionStatus, Turn;
169191
+ var init_turn = __esm({
169192
+ "packages/core/dist/src/core/turn.js"() {
169306
169193
  "use strict";
169307
169194
  init_esbuild_shims();
169308
- init_async_mutex();
169309
- init_debugLogger();
169310
- debugLogger21 = createDebugLogger("JSONL");
169311
- fileLocks = /* @__PURE__ */ new Map();
169312
- __name(getFileLock, "getFileLock");
169313
- __name(readLines, "readLines");
169314
- __name(read, "read");
169315
- __name(writeLine, "writeLine");
169316
- __name(writeLineSync, "writeLineSync");
169317
- __name(write, "write");
169318
- __name(countLines, "countLines");
169319
- __name(exists, "exists");
169195
+ init_node();
169196
+ init_partUtils();
169197
+ init_errorReporting();
169198
+ init_errors();
169199
+ init_thoughtUtils();
169200
+ init_streamStall();
169201
+ STREAM_STALL_TIMEOUT_MS = parseInt(process.env["PROTO_STREAM_STALL_TIMEOUT_MS"] ?? "300000", 10);
169202
+ (function(GeminiEventType2) {
169203
+ GeminiEventType2["Content"] = "content";
169204
+ GeminiEventType2["ToolCallRequest"] = "tool_call_request";
169205
+ GeminiEventType2["ToolCallResponse"] = "tool_call_response";
169206
+ GeminiEventType2["ToolCallConfirmation"] = "tool_call_confirmation";
169207
+ GeminiEventType2["UserCancelled"] = "user_cancelled";
169208
+ GeminiEventType2["Error"] = "error";
169209
+ GeminiEventType2["ChatCompressed"] = "chat_compressed";
169210
+ GeminiEventType2["Thought"] = "thought";
169211
+ GeminiEventType2["MaxSessionTurns"] = "max_session_turns";
169212
+ GeminiEventType2["SessionTokenLimitExceeded"] = "session_token_limit_exceeded";
169213
+ GeminiEventType2["Finished"] = "finished";
169214
+ GeminiEventType2["LoopDetected"] = "loop_detected";
169215
+ GeminiEventType2["Citation"] = "citation";
169216
+ GeminiEventType2["Retry"] = "retry";
169217
+ GeminiEventType2["HookSystemMessage"] = "hook_system_message";
169218
+ })(GeminiEventType || (GeminiEventType = {}));
169219
+ (function(CompressionStatus2) {
169220
+ CompressionStatus2[CompressionStatus2["COMPRESSED"] = 1] = "COMPRESSED";
169221
+ CompressionStatus2[CompressionStatus2["COMPRESSION_FAILED_INFLATED_TOKEN_COUNT"] = 2] = "COMPRESSION_FAILED_INFLATED_TOKEN_COUNT";
169222
+ CompressionStatus2[CompressionStatus2["COMPRESSION_FAILED_TOKEN_COUNT_ERROR"] = 3] = "COMPRESSION_FAILED_TOKEN_COUNT_ERROR";
169223
+ CompressionStatus2[CompressionStatus2["COMPRESSION_FAILED_EMPTY_SUMMARY"] = 4] = "COMPRESSION_FAILED_EMPTY_SUMMARY";
169224
+ CompressionStatus2[CompressionStatus2["NOOP"] = 5] = "NOOP";
169225
+ })(CompressionStatus || (CompressionStatus = {}));
169226
+ Turn = class {
169227
+ static {
169228
+ __name(this, "Turn");
169229
+ }
169230
+ chat;
169231
+ prompt_id;
169232
+ pendingToolCalls = [];
169233
+ debugResponses = [];
169234
+ pendingCitations = /* @__PURE__ */ new Set();
169235
+ finishReason = void 0;
169236
+ currentResponseId;
169237
+ constructor(chat, prompt_id) {
169238
+ this.chat = chat;
169239
+ this.prompt_id = prompt_id;
169240
+ }
169241
+ // The run method yields simpler events suitable for server logic
169242
+ async *run(model, req, signal) {
169243
+ try {
169244
+ const rawStream = await this.chat.sendMessageStream(model, {
169245
+ message: req,
169246
+ config: {
169247
+ abortSignal: signal
169248
+ }
169249
+ }, this.prompt_id);
169250
+ const responseStream = withChunkTimeout(rawStream, STREAM_STALL_TIMEOUT_MS);
169251
+ for await (const streamEvent of responseStream) {
169252
+ if (signal?.aborted) {
169253
+ yield { type: GeminiEventType.UserCancelled };
169254
+ return;
169255
+ }
169256
+ if (streamEvent.type === "retry") {
169257
+ yield {
169258
+ type: GeminiEventType.Retry,
169259
+ retryInfo: streamEvent.retryInfo
169260
+ };
169261
+ continue;
169262
+ }
169263
+ const resp = streamEvent.value;
169264
+ if (!resp)
169265
+ continue;
169266
+ this.debugResponses.push(resp);
169267
+ if (resp.responseId) {
169268
+ this.currentResponseId = resp.responseId;
169269
+ }
169270
+ const thoughtText = getThoughtText(resp);
169271
+ if (thoughtText) {
169272
+ yield {
169273
+ type: GeminiEventType.Thought,
169274
+ value: parseThought(thoughtText)
169275
+ };
169276
+ }
169277
+ const text = getResponseText(resp);
169278
+ if (text) {
169279
+ yield { type: GeminiEventType.Content, value: text };
169280
+ }
169281
+ const functionCalls = resp.functionCalls ?? [];
169282
+ for (const fnCall of functionCalls) {
169283
+ const event = this.handlePendingFunctionCall(fnCall);
169284
+ if (event) {
169285
+ yield event;
169286
+ }
169287
+ }
169288
+ for (const citation of getCitations(resp)) {
169289
+ this.pendingCitations.add(citation);
169290
+ }
169291
+ const finishReason = resp.candidates?.[0]?.finishReason;
169292
+ if (finishReason) {
169293
+ if (finishReason === FinishReason.MAX_TOKENS) {
169294
+ for (const tc of this.pendingToolCalls) {
169295
+ tc.wasOutputTruncated = true;
169296
+ }
169297
+ }
169298
+ if (this.pendingCitations.size > 0) {
169299
+ yield {
169300
+ type: GeminiEventType.Citation,
169301
+ value: `Citations:
169302
+ ${[...this.pendingCitations].sort().join("\n")}`
169303
+ };
169304
+ this.pendingCitations.clear();
169305
+ }
169306
+ this.finishReason = finishReason;
169307
+ yield {
169308
+ type: GeminiEventType.Finished,
169309
+ value: {
169310
+ reason: finishReason,
169311
+ usageMetadata: resp.usageMetadata
169312
+ }
169313
+ };
169314
+ }
169315
+ }
169316
+ } catch (e4) {
169317
+ if (signal.aborted) {
169318
+ yield { type: GeminiEventType.UserCancelled };
169319
+ return;
169320
+ }
169321
+ if (e4 instanceof StreamStallError) {
169322
+ yield {
169323
+ type: GeminiEventType.Error,
169324
+ value: {
169325
+ error: {
169326
+ message: e4.message,
169327
+ status: void 0
169328
+ }
169329
+ }
169330
+ };
169331
+ return;
169332
+ }
169333
+ const error40 = toFriendlyError(e4);
169334
+ if (error40 instanceof UnauthorizedError) {
169335
+ throw error40;
169336
+ }
169337
+ const contextForReport = [...this.chat.getHistory(
169338
+ /*curated*/
169339
+ true
169340
+ ), req];
169341
+ await reportError2(error40, "Error when talking to API", contextForReport, "Turn.run-sendMessageStream");
169342
+ const status = typeof error40 === "object" && error40 !== null && "status" in error40 && typeof error40.status === "number" ? error40.status : void 0;
169343
+ const structuredError = {
169344
+ message: getErrorMessage(error40),
169345
+ status
169346
+ };
169347
+ await this.chat.maybeIncludeSchemaDepthContext(structuredError);
169348
+ yield { type: GeminiEventType.Error, value: { error: structuredError } };
169349
+ return;
169350
+ }
169351
+ }
169352
+ handlePendingFunctionCall(fnCall) {
169353
+ const callId = fnCall.id ?? `${fnCall.name}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
169354
+ const name4 = fnCall.name || "undefined_tool_name";
169355
+ const args2 = fnCall.args || {};
169356
+ const toolCallRequest = {
169357
+ callId,
169358
+ name: name4,
169359
+ args: args2,
169360
+ isClientInitiated: false,
169361
+ prompt_id: this.prompt_id,
169362
+ response_id: this.currentResponseId
169363
+ };
169364
+ this.pendingToolCalls.push(toolCallRequest);
169365
+ return { type: GeminiEventType.ToolCallRequest, value: toolCallRequest };
169366
+ }
169367
+ getDebugResponses() {
169368
+ return this.debugResponses;
169369
+ }
169370
+ };
169371
+ __name(getCitations, "getCitations");
169320
169372
  }
169321
169373
  });
169322
169374
 
169323
169375
  // packages/core/dist/src/utils/gitUtils.js
169324
- import * as fs18 from "node:fs";
169325
- import * as path17 from "node:path";
169376
+ import * as fs17 from "node:fs";
169377
+ import * as path16 from "node:path";
169326
169378
  import { execSync as execSync2 } from "node:child_process";
169327
169379
  function isGitRepository(directory) {
169328
169380
  try {
169329
- let currentDir = path17.resolve(directory);
169381
+ let currentDir = path16.resolve(directory);
169330
169382
  while (true) {
169331
- const gitDir = path17.join(currentDir, ".git");
169332
- if (fs18.existsSync(gitDir)) {
169383
+ const gitDir = path16.join(currentDir, ".git");
169384
+ if (fs17.existsSync(gitDir)) {
169333
169385
  return true;
169334
169386
  }
169335
- const parentDir = path17.dirname(currentDir);
169387
+ const parentDir = path16.dirname(currentDir);
169336
169388
  if (parentDir === currentDir) {
169337
169389
  break;
169338
169390
  }
@@ -169345,13 +169397,13 @@ function isGitRepository(directory) {
169345
169397
  }
169346
169398
  function findGitRoot(directory) {
169347
169399
  try {
169348
- let currentDir = path17.resolve(directory);
169400
+ let currentDir = path16.resolve(directory);
169349
169401
  while (true) {
169350
- const gitDir = path17.join(currentDir, ".git");
169351
- if (fs18.existsSync(gitDir)) {
169402
+ const gitDir = path16.join(currentDir, ".git");
169403
+ if (fs17.existsSync(gitDir)) {
169352
169404
  return currentDir;
169353
169405
  }
169354
- const parentDir = path17.dirname(currentDir);
169406
+ const parentDir = path16.dirname(currentDir);
169355
169407
  if (parentDir === currentDir) {
169356
169408
  break;
169357
169409
  }
@@ -169410,985 +169462,102 @@ var init_gitUtils = __esm({
169410
169462
  }
169411
169463
  const gitRoot = findGitRoot(cwd6);
169412
169464
  if (gitRoot) {
169413
- return path17.basename(gitRoot);
169465
+ return path16.basename(gitRoot);
169414
169466
  }
169415
169467
  return void 0;
169416
169468
  }, "getGitRepoName");
169417
169469
  }
169418
169470
  });
169419
169471
 
169420
- // packages/core/dist/src/services/chatRecordingService.js
169421
- import path18 from "node:path";
169422
- import fs19 from "node:fs";
169423
- import { randomUUID as randomUUID2 } from "node:crypto";
169424
- var debugLogger22, ChatRecordingService;
169425
- var init_chatRecordingService = __esm({
169426
- "packages/core/dist/src/services/chatRecordingService.js"() {
169427
- "use strict";
169428
- init_esbuild_shims();
169429
- init_config3();
169430
- init_node();
169431
- init_jsonl_utils();
169432
- init_gitUtils();
169433
- init_debugLogger();
169434
- debugLogger22 = createDebugLogger("CHAT_RECORDING");
169435
- ChatRecordingService = class {
169436
- static {
169437
- __name(this, "ChatRecordingService");
169438
- }
169439
- /** UUID of the last written record in the chain */
169440
- lastRecordUuid = null;
169441
- config;
169442
- /** In-memory cache of the current session's custom title (for re-append on exit) */
169443
- currentCustomTitle;
169444
- constructor(config2) {
169445
- this.config = config2;
169446
- this.lastRecordUuid = config2.getResumedSessionData()?.lastCompletedUuid ?? null;
169447
- if (config2.getResumedSessionData()) {
169448
- try {
169449
- const sessionService = config2.getSessionService();
169450
- this.currentCustomTitle = sessionService.getSessionTitle(config2.getSessionId());
169451
- this.finalize();
169452
- } catch {
169453
- }
169454
- }
169455
- }
169456
- /**
169457
- * Returns the session ID.
169458
- * @returns The session ID.
169459
- */
169460
- getSessionId() {
169461
- return this.config.getSessionId();
169462
- }
169463
- /**
169464
- * Ensures the chats directory exists, creating it if it doesn't exist.
169465
- * @returns The path to the chats directory.
169466
- * @throws Error if the directory cannot be created.
169467
- */
169468
- ensureChatsDir() {
169469
- const projectDir = this.config.storage.getProjectDir();
169470
- const chatsDir = path18.join(projectDir, "chats");
169471
- try {
169472
- fs19.mkdirSync(chatsDir, { recursive: true });
169473
- } catch {
169474
- }
169475
- return chatsDir;
169476
- }
169477
- /**
169478
- * Ensures the conversation file exists, creating it if it doesn't exist.
169479
- * Uses atomic file creation to avoid race conditions.
169480
- * @returns The path to the conversation file.
169481
- * @throws Error if the file cannot be created or accessed.
169482
- */
169483
- ensureConversationFile() {
169484
- const chatsDir = this.ensureChatsDir();
169485
- const sessionId = this.getSessionId();
169486
- const safeFilename = `${sessionId}.jsonl`;
169487
- const conversationFile = path18.join(chatsDir, safeFilename);
169488
- if (fs19.existsSync(conversationFile)) {
169489
- return conversationFile;
169490
- }
169491
- try {
169492
- fs19.writeFileSync(conversationFile, "", { flag: "wx", encoding: "utf8" });
169493
- } catch (error40) {
169494
- const nodeError = error40;
169495
- if (nodeError.code !== "EEXIST") {
169496
- const message = error40 instanceof Error ? error40.message : String(error40);
169497
- throw new Error(`Failed to create conversation file at ${conversationFile}: ${message}`);
169498
- }
169499
- }
169500
- return conversationFile;
169501
- }
169502
- /**
169503
- * Creates base fields for a ChatRecord.
169504
- */
169505
- createBaseRecord(type) {
169506
- return {
169507
- uuid: randomUUID2(),
169508
- parentUuid: this.lastRecordUuid,
169509
- sessionId: this.getSessionId(),
169510
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
169511
- type,
169512
- cwd: this.config.getProjectRoot(),
169513
- version: this.config.getCliVersion() || "unknown",
169514
- gitBranch: getGitBranch(this.config.getProjectRoot())
169515
- };
169516
- }
169517
- /**
169518
- * Appends a record to the session file and updates lastRecordUuid.
169519
- */
169520
- appendRecord(record2) {
169521
- try {
169522
- const conversationFile = this.ensureConversationFile();
169523
- writeLineSync(conversationFile, record2);
169524
- this.lastRecordUuid = record2.uuid;
169525
- } catch (error40) {
169526
- debugLogger22.error("Error appending record:", error40);
169527
- throw error40;
169528
- }
169529
- }
169530
- /**
169531
- * Records a user message.
169532
- * Writes immediately to disk.
169533
- *
169534
- * @param message The raw PartListUnion object as used with the API
169535
- */
169536
- recordUserMessage(message) {
169537
- try {
169538
- const record2 = {
169539
- ...this.createBaseRecord("user"),
169540
- message: createUserContent(message)
169541
- };
169542
- this.appendRecord(record2);
169543
- } catch (error40) {
169544
- debugLogger22.error("Error saving user message:", error40);
169545
- }
169546
- }
169547
- /**
169548
- * Records an assistant turn with all available data.
169549
- * Writes immediately to disk.
169550
- *
169551
- * @param data.message The raw PartListUnion object from the model response
169552
- * @param data.model The model name
169553
- * @param data.tokens Token usage statistics
169554
- * @param data.contextWindowSize Context window size of the model
169555
- * @param data.toolCallsMetadata Enriched tool call info for UI recovery
169556
- */
169557
- recordAssistantTurn(data) {
169558
- try {
169559
- const record2 = {
169560
- ...this.createBaseRecord("assistant"),
169561
- model: data.model
169562
- };
169563
- if (data.message !== void 0) {
169564
- record2.message = createModelContent(data.message);
169565
- }
169566
- if (data.tokens) {
169567
- record2.usageMetadata = data.tokens;
169568
- }
169569
- if (data.contextWindowSize !== void 0) {
169570
- record2.contextWindowSize = data.contextWindowSize;
169571
- }
169572
- this.appendRecord(record2);
169573
- } catch (error40) {
169574
- debugLogger22.error("Error saving assistant turn:", error40);
169575
- }
169576
- }
169577
- /**
169578
- * Records tool results (function responses) sent back to the model.
169579
- * Writes immediately to disk.
169580
- *
169581
- * @param message The raw PartListUnion object with functionResponse parts
169582
- * @param toolCallResult Optional tool call result info for UI recovery
169583
- */
169584
- recordToolResult(message, toolCallResult) {
169585
- try {
169586
- const record2 = {
169587
- ...this.createBaseRecord("tool_result"),
169588
- message: createUserContent(message)
169589
- };
169590
- if (toolCallResult) {
169591
- if (typeof toolCallResult.resultDisplay === "object" && toolCallResult.resultDisplay !== null && "type" in toolCallResult.resultDisplay && toolCallResult.resultDisplay.type === "task_execution") {
169592
- const taskResult = toolCallResult.resultDisplay;
169593
- record2.toolCallResult = {
169594
- ...toolCallResult,
169595
- resultDisplay: {
169596
- ...taskResult,
169597
- toolCalls: []
169598
- }
169599
- };
169600
- } else {
169601
- record2.toolCallResult = toolCallResult;
169602
- }
169603
- }
169604
- this.appendRecord(record2);
169605
- } catch (error40) {
169606
- debugLogger22.error("Error saving tool result:", error40);
169607
- }
169608
- }
169609
- /**
169610
- * Records a slash command invocation as a system record. This keeps the model
169611
- * history clean while allowing resume to replay UI output for commands like
169612
- * /about.
169613
- */
169614
- recordSlashCommand(payload) {
169615
- try {
169616
- const record2 = {
169617
- ...this.createBaseRecord("system"),
169618
- type: "system",
169619
- subtype: "slash_command",
169620
- systemPayload: payload
169621
- };
169622
- this.appendRecord(record2);
169623
- } catch (error40) {
169624
- debugLogger22.error("Error saving slash command record:", error40);
169625
- }
169626
- }
169627
- /**
169628
- * Records a chat compression checkpoint as a system record. This keeps the UI
169629
- * history immutable while allowing resume/continue flows to reconstruct the
169630
- * compressed model-facing history from the stored snapshot.
169631
- */
169632
- recordChatCompression(payload) {
169633
- try {
169634
- const record2 = {
169635
- ...this.createBaseRecord("system"),
169636
- type: "system",
169637
- subtype: "chat_compression",
169638
- systemPayload: payload
169639
- };
169640
- this.appendRecord(record2);
169641
- } catch (error40) {
169642
- debugLogger22.error("Error saving chat compression record:", error40);
169643
- }
169644
- }
169645
- /**
169646
- * Records a UI telemetry event for replaying metrics on resume.
169647
- */
169648
- recordUiTelemetryEvent(uiEvent) {
169649
- try {
169650
- const record2 = {
169651
- ...this.createBaseRecord("system"),
169652
- type: "system",
169653
- subtype: "ui_telemetry",
169654
- systemPayload: { uiEvent }
169655
- };
169656
- this.appendRecord(record2);
169657
- } catch (error40) {
169658
- debugLogger22.error("Error saving ui telemetry record:", error40);
169659
- }
169660
- }
169661
- /**
169662
- * Records a custom title for the session (set via /rename).
169663
- * Appended as a system record so it persists with the session data.
169664
- * Also caches the title in memory for re-append on shutdown.
169665
- *
169666
- * @returns true if the record was written successfully, false on I/O error.
169667
- */
169668
- recordCustomTitle(customTitle) {
169669
- try {
169670
- const record2 = {
169671
- ...this.createBaseRecord("system"),
169672
- type: "system",
169673
- subtype: "custom_title",
169674
- systemPayload: { customTitle }
169675
- };
169676
- this.appendRecord(record2);
169677
- this.currentCustomTitle = customTitle;
169678
- return true;
169679
- } catch (error40) {
169680
- debugLogger22.error("Error saving custom title record:", error40);
169681
- return false;
169682
- }
169683
- }
169684
- /**
169685
- * Finalizes the current session by re-appending cached metadata to EOF.
169686
- *
169687
- * Call this whenever leaving the current session — whether switching to
169688
- * another session, shutting down the process, or any other transition.
169689
- * This single entry point replaces scattered re-append calls and ensures
169690
- * the custom_title record stays within the last 64KB tail window that
169691
- * readSessionTitleFromFile() scans.
169692
- *
169693
- * Best-effort: errors are logged but never thrown.
169694
- */
169695
- finalize() {
169696
- if (!this.currentCustomTitle) {
169697
- return;
169698
- }
169699
- try {
169700
- const record2 = {
169701
- ...this.createBaseRecord("system"),
169702
- type: "system",
169703
- subtype: "custom_title",
169704
- systemPayload: { customTitle: this.currentCustomTitle }
169705
- };
169706
- this.appendRecord(record2);
169707
- } catch (error40) {
169708
- debugLogger22.error("Error finalizing session metadata:", error40);
169709
- }
169710
- }
169711
- /**
169712
- * Records @-command metadata as a system record for UI reconstruction.
169713
- */
169714
- recordAtCommand(payload) {
169715
- try {
169716
- const record2 = {
169717
- ...this.createBaseRecord("system"),
169718
- type: "system",
169719
- subtype: "at_command",
169720
- systemPayload: payload
169721
- };
169722
- this.appendRecord(record2);
169723
- } catch (error40) {
169724
- debugLogger22.error("Error saving @-command record:", error40);
169725
- }
169726
- }
169727
- };
169728
- }
169729
- });
169730
-
169731
- // packages/core/dist/src/core/geminiChat.js
169732
- function isValidResponse2(response) {
169733
- if (response.usageMetadata) {
169734
- return true;
169472
+ // packages/core/dist/src/core/prompts.js
169473
+ import path17 from "node:path";
169474
+ import fs18 from "node:fs";
169475
+ import os6 from "node:os";
169476
+ import { execSync as execSync3 } from "node:child_process";
169477
+ import process3 from "node:process";
169478
+ function assemblePromptSections(sections) {
169479
+ const stable = sections.filter((s5) => s5.volatility === "stable" && s5.content);
169480
+ const workspace = sections.filter((s5) => s5.volatility === "workspace" && s5.content);
169481
+ const run3 = sections.filter((s5) => s5.volatility === "run" && s5.content);
169482
+ const stablePart = stable.map((s5) => s5.content).join("\n\n");
169483
+ const nonStable = [...workspace, ...run3].map((s5) => s5.content).join("\n\n");
169484
+ if (!stablePart)
169485
+ return nonStable;
169486
+ if (!nonStable)
169487
+ return stablePart;
169488
+ return `${stablePart}${CACHE_BOUNDARY_SENTINEL}${nonStable}`;
169489
+ }
169490
+ function resolvePathFromEnv(envVar) {
169491
+ const trimmedEnvVar = envVar?.trim();
169492
+ if (!trimmedEnvVar) {
169493
+ return { isSwitch: false, value: null, isDisabled: false };
169735
169494
  }
169736
- if (response.candidates === void 0 || response.candidates.length === 0) {
169737
- return false;
169495
+ const lowerEnvVar = trimmedEnvVar.toLowerCase();
169496
+ if (["0", "false", "1", "true"].includes(lowerEnvVar)) {
169497
+ const isDisabled = ["0", "false"].includes(lowerEnvVar);
169498
+ return { isSwitch: true, value: lowerEnvVar, isDisabled };
169738
169499
  }
169739
- if (response.candidates.some((candidate) => candidate.finishReason)) {
169740
- return true;
169500
+ let customPath = trimmedEnvVar;
169501
+ if (customPath.startsWith("~/") || customPath === "~") {
169502
+ try {
169503
+ const home = os6.homedir();
169504
+ if (customPath === "~") {
169505
+ customPath = home;
169506
+ } else {
169507
+ customPath = path17.join(home, customPath.slice(2));
169508
+ }
169509
+ } catch (error40) {
169510
+ debugLogger21.warn(`Could not resolve home directory for path: ${trimmedEnvVar}`, error40);
169511
+ return { isSwitch: false, value: null, isDisabled: false };
169512
+ }
169741
169513
  }
169742
- const content = response.candidates[0]?.content;
169743
- return content !== void 0 && isValidContent2(content);
169744
- }
169745
- function isValidNonThoughtTextPart(part) {
169746
- return typeof part.text === "string" && !part.thought && !part.thoughtSignature && // Technically, the model should never generate parts that have text and
169747
- // any of these but we don't trust them so check anyways.
169748
- !part.functionCall && !part.functionResponse && !part.inlineData && !part.fileData;
169514
+ return {
169515
+ isSwitch: false,
169516
+ value: path17.resolve(customPath),
169517
+ isDisabled: false
169518
+ };
169749
169519
  }
169750
- function isValidContent2(content) {
169751
- if (content.parts === void 0 || content.parts.length === 0) {
169752
- return false;
169753
- }
169754
- for (const part of content.parts) {
169755
- if (part === void 0 || Object.keys(part).length === 0) {
169756
- return false;
169757
- }
169758
- if (!isValidContentPart(part)) {
169759
- return false;
169760
- }
169520
+ function getCustomSystemPrompt(customInstruction, userMemory, appendInstruction) {
169521
+ let instructionText = "";
169522
+ if (typeof customInstruction === "string") {
169523
+ instructionText = customInstruction;
169524
+ } else if (Array.isArray(customInstruction)) {
169525
+ instructionText = customInstruction.map((part) => typeof part === "string" ? part : part.text || "").join("");
169526
+ } else if (customInstruction && "parts" in customInstruction) {
169527
+ instructionText = customInstruction.parts?.map((part) => typeof part === "string" ? part : part.text || "").join("") || "";
169528
+ } else if (customInstruction && "text" in customInstruction) {
169529
+ instructionText = customInstruction.text || "";
169761
169530
  }
169762
- return true;
169531
+ const memorySuffix = buildSystemPromptSuffix(userMemory);
169532
+ return `${instructionText}${memorySuffix}${buildSystemPromptSuffix(appendInstruction)}`;
169763
169533
  }
169764
- function isValidContentPart(part) {
169765
- const isInvalid = !part.thought && !part.thoughtSignature && part.text !== void 0 && part.text === "" && part.functionCall === void 0;
169766
- return !isInvalid;
169534
+ function buildSystemPromptSuffix(text) {
169535
+ const trimmed2 = text?.trim();
169536
+ return trimmed2 ? `
169537
+
169538
+ ---
169539
+
169540
+ ${trimmed2}` : "";
169767
169541
  }
169768
- function validateHistory2(history) {
169769
- for (const content of history) {
169770
- if (content.role !== "user" && content.role !== "model") {
169771
- throw new Error(`Role must be user or model, but got ${content.role}.`);
169542
+ function getCoreSystemPrompt(userMemory, model, appendInstruction, interactive = false) {
169543
+ let systemMdEnabled = false;
169544
+ let systemMdPath = path17.resolve(path17.join(QWEN_CONFIG_DIR, "system.md"));
169545
+ const systemMdResolution = resolvePathFromEnv(process3.env["QWEN_SYSTEM_MD"]);
169546
+ if (systemMdResolution.value && !systemMdResolution.isDisabled) {
169547
+ systemMdEnabled = true;
169548
+ if (!systemMdResolution.isSwitch) {
169549
+ systemMdPath = systemMdResolution.value;
169550
+ }
169551
+ if (!fs18.existsSync(systemMdPath)) {
169552
+ throw new Error(`missing system prompt file '${systemMdPath}'`);
169772
169553
  }
169773
169554
  }
169774
- }
169775
- function extractCuratedHistory2(comprehensiveHistory) {
169776
- if (comprehensiveHistory === void 0 || comprehensiveHistory.length === 0) {
169777
- return [];
169778
- }
169779
- const curatedHistory = [];
169780
- const length = comprehensiveHistory.length;
169781
- let i4 = 0;
169782
- while (i4 < length) {
169783
- if (comprehensiveHistory[i4].role === "user") {
169784
- curatedHistory.push(comprehensiveHistory[i4]);
169785
- i4++;
169786
- } else {
169787
- const modelOutput = [];
169788
- let isValid2 = true;
169789
- while (i4 < length && comprehensiveHistory[i4].role === "model") {
169790
- modelOutput.push(comprehensiveHistory[i4]);
169791
- if (isValid2 && !isValidContent2(comprehensiveHistory[i4])) {
169792
- isValid2 = false;
169793
- }
169794
- i4++;
169795
- }
169796
- if (isValid2) {
169797
- curatedHistory.push(...modelOutput);
169798
- }
169799
- }
169800
- }
169801
- return curatedHistory;
169802
- }
169803
- function hasTruncationCascade(contents) {
169804
- if (contents.length === 0)
169805
- return false;
169806
- const last2 = contents[contents.length - 1];
169807
- if (last2.role !== "user")
169808
- return false;
169809
- return last2.parts?.some((p2) => {
169810
- const err3 = p2.functionResponse?.response?.["error"];
169811
- return typeof err3 === "string" && err3.includes(TRUNCATION_CASCADE_MARKER);
169812
- }) ?? false;
169813
- }
169814
- function trimLargeToolResponsesFromContext(contents) {
169815
- return contents.map((content) => {
169816
- if (content.role !== "user" || !content.parts)
169817
- return content;
169818
- const needsTrim = content.parts.some((p2) => {
169819
- const out2 = p2.functionResponse?.response?.["output"];
169820
- return typeof out2 === "string" && out2.length > LARGE_TOOL_RESPONSE_TRIM_CHARS;
169821
- });
169822
- if (!needsTrim)
169823
- return content;
169824
- return {
169825
- ...content,
169826
- parts: content.parts.map((p2) => {
169827
- const out2 = p2.functionResponse?.response?.["output"];
169828
- if (typeof out2 !== "string" || out2.length <= LARGE_TOOL_RESPONSE_TRIM_CHARS)
169829
- return p2;
169830
- const trimmed2 = out2.slice(0, LARGE_TOOL_RESPONSE_TRIM_CHARS) + `
169831
-
169832
- [... output trimmed from ${out2.length} to ${LARGE_TOOL_RESPONSE_TRIM_CHARS} chars to reduce context size ...]`;
169833
- return {
169834
- ...p2,
169835
- functionResponse: {
169836
- ...p2.functionResponse,
169837
- response: {
169838
- ...p2.functionResponse.response,
169839
- output: trimmed2
169840
- }
169841
- }
169842
- };
169843
- })
169844
- };
169845
- });
169846
- }
169847
- function trimToolErrorsFromContext(contents, maxTrimPairs = 6) {
169848
- const trimmed2 = [...contents];
169849
- let trimmed_count = 0;
169850
- while (trimmed2.length >= 2 && trimmed_count < maxTrimPairs) {
169851
- const last2 = trimmed2[trimmed2.length - 1];
169852
- const prev = trimmed2[trimmed2.length - 2];
169853
- if (last2.role !== "user")
169854
- break;
169855
- const allErrors = last2.parts?.every((p2) => p2.functionResponse !== void 0 && typeof p2.functionResponse.response?.["error"] === "string");
169856
- if (!allErrors)
169857
- break;
169858
- if (prev.role !== "model")
169859
- break;
169860
- const hasToolCall2 = prev.parts?.some((p2) => p2.functionCall !== void 0);
169861
- if (!hasToolCall2)
169862
- break;
169863
- trimmed2.splice(trimmed2.length - 2, 2);
169864
- trimmed_count++;
169865
- }
169866
- return trimmed2;
169867
- }
169868
- function isSchemaDepthError(errorMessage) {
169869
- return errorMessage.includes("maximum schema depth exceeded");
169870
- }
169871
- function isInvalidArgumentError(errorMessage) {
169872
- return errorMessage.includes("Request contains an invalid argument");
169873
- }
169874
- var debugLogger23, StreamEventType, INVALID_CONTENT_RETRY_OPTIONS, INVALID_STREAM_RETRY_CONFIG, RATE_LIMIT_RETRY_OPTIONS, TRUNCATION_CASCADE_MARKER, LARGE_TOOL_RESPONSE_TRIM_CHARS, InvalidStreamError, GeminiChat;
169875
- var init_geminiChat = __esm({
169876
- "packages/core/dist/src/core/geminiChat.js"() {
169877
- "use strict";
169878
- init_esbuild_shims();
169879
- init_node();
169880
- init_retry();
169881
- init_errors();
169882
- init_debugLogger();
169883
- init_errorParsing();
169884
- init_rateLimit();
169885
- init_tools();
169886
- init_loggers();
169887
- init_chatRecordingService();
169888
- init_types4();
169889
- debugLogger23 = createDebugLogger("QWEN_CODE_CHAT");
169890
- (function(StreamEventType2) {
169891
- StreamEventType2["CHUNK"] = "chunk";
169892
- StreamEventType2["RETRY"] = "retry";
169893
- })(StreamEventType || (StreamEventType = {}));
169894
- INVALID_CONTENT_RETRY_OPTIONS = {
169895
- maxAttempts: 2,
169896
- // 1 initial call + 1 retry
169897
- initialDelayMs: 500
169898
- };
169899
- INVALID_STREAM_RETRY_CONFIG = {
169900
- maxRetries: 2,
169901
- initialDelayMs: 2e3
169902
- };
169903
- RATE_LIMIT_RETRY_OPTIONS = {
169904
- maxRetries: 10,
169905
- delayMs: 6e4
169906
- };
169907
- __name(isValidResponse2, "isValidResponse");
169908
- __name(isValidNonThoughtTextPart, "isValidNonThoughtTextPart");
169909
- __name(isValidContent2, "isValidContent");
169910
- __name(isValidContentPart, "isValidContentPart");
169911
- __name(validateHistory2, "validateHistory");
169912
- __name(extractCuratedHistory2, "extractCuratedHistory");
169913
- TRUNCATION_CASCADE_MARKER = "truncated due to max_tokens limit";
169914
- LARGE_TOOL_RESPONSE_TRIM_CHARS = 1e4;
169915
- __name(hasTruncationCascade, "hasTruncationCascade");
169916
- __name(trimLargeToolResponsesFromContext, "trimLargeToolResponsesFromContext");
169917
- __name(trimToolErrorsFromContext, "trimToolErrorsFromContext");
169918
- InvalidStreamError = class extends Error {
169919
- static {
169920
- __name(this, "InvalidStreamError");
169921
- }
169922
- type;
169923
- constructor(message, type) {
169924
- super(message);
169925
- this.name = "InvalidStreamError";
169926
- this.type = type;
169927
- }
169928
- };
169929
- GeminiChat = class {
169930
- static {
169931
- __name(this, "GeminiChat");
169932
- }
169933
- config;
169934
- generationConfig;
169935
- history;
169936
- chatRecordingService;
169937
- telemetryService;
169938
- // A promise to represent the current state of the message being sent to the
169939
- // model.
169940
- sendPromise = Promise.resolve();
169941
- /**
169942
- * Creates a new GeminiChat instance.
169943
- *
169944
- * @param config - The configuration object.
169945
- * @param generationConfig - Optional generation configuration.
169946
- * @param history - Optional initial conversation history.
169947
- * @param chatRecordingService - Optional recording service. If provided, chat
169948
- * messages will be recorded.
169949
- * @param telemetryService - Optional UI telemetry service. When provided,
169950
- * prompt token counts are reported on each API response. Pass `undefined`
169951
- * for sub-agent chats to avoid overwriting the main agent's context usage.
169952
- */
169953
- constructor(config2, generationConfig = {}, history = [], chatRecordingService, telemetryService) {
169954
- this.config = config2;
169955
- this.generationConfig = generationConfig;
169956
- this.history = history;
169957
- this.chatRecordingService = chatRecordingService;
169958
- this.telemetryService = telemetryService;
169959
- validateHistory2(history);
169960
- }
169961
- setSystemInstruction(sysInstr) {
169962
- this.generationConfig.systemInstruction = sysInstr;
169963
- }
169964
- /**
169965
- * Sends a message to the model and returns the response in chunks.
169966
- *
169967
- * @remarks
169968
- * This method will wait for the previous message to be processed before
169969
- * sending the next message.
169970
- *
169971
- * @see {@link Chat#sendMessage} for non-streaming method.
169972
- * @param params - parameters for sending the message.
169973
- * @return The model's response.
169974
- *
169975
- * @example
169976
- * ```ts
169977
- * const chat = ai.chats.create({model: 'gemini-2.0-flash'});
169978
- * const response = await chat.sendMessageStream({
169979
- * message: 'Why is the sky blue?'
169980
- * });
169981
- * for await (const chunk of response) {
169982
- * console.log(chunk.text);
169983
- * }
169984
- * ```
169985
- */
169986
- async sendMessageStream(model, params, prompt_id) {
169987
- await this.sendPromise;
169988
- let streamDoneResolver;
169989
- const streamDonePromise = new Promise((resolve37) => {
169990
- streamDoneResolver = resolve37;
169991
- });
169992
- this.sendPromise = streamDonePromise;
169993
- const userContent = createUserContent(params.message);
169994
- this.history.push(userContent);
169995
- let requestContents = this.getHistory(true);
169996
- if (hasTruncationCascade(requestContents)) {
169997
- const withoutErrors = trimToolErrorsFromContext(requestContents);
169998
- requestContents = trimLargeToolResponsesFromContext(withoutErrors);
169999
- debugLogger23.warn(`MAX_TOKENS cascade detected: trimmed context from ${this.getHistory(true).length} to ${requestContents.length} entries and capped large tool responses.`);
170000
- }
170001
- const self2 = this;
170002
- return async function* () {
170003
- try {
170004
- let lastError = new Error("Request failed after all retries.");
170005
- let rateLimitRetryCount = 0;
170006
- let invalidStreamRetryCount = 0;
170007
- const cgConfig = self2.config.getContentGeneratorConfig();
170008
- const maxRateLimitRetries = cgConfig?.maxRetries ?? RATE_LIMIT_RETRY_OPTIONS.maxRetries;
170009
- const extraRetryErrorCodes = cgConfig?.retryErrorCodes;
170010
- for (let attempt = 0; attempt < INVALID_CONTENT_RETRY_OPTIONS.maxAttempts; attempt++) {
170011
- try {
170012
- if (attempt > 0 || rateLimitRetryCount > 0 || invalidStreamRetryCount > 0) {
170013
- yield { type: StreamEventType.RETRY };
170014
- }
170015
- const stream2 = await self2.makeApiCallAndProcessStream(model, requestContents, params, prompt_id);
170016
- for await (const chunk of stream2) {
170017
- yield { type: StreamEventType.CHUNK, value: chunk };
170018
- }
170019
- lastError = null;
170020
- break;
170021
- } catch (error40) {
170022
- lastError = error40;
170023
- const isRateLimit = isRateLimitError(error40, extraRetryErrorCodes);
170024
- if (isRateLimit && rateLimitRetryCount < maxRateLimitRetries) {
170025
- rateLimitRetryCount++;
170026
- const delayMs = RATE_LIMIT_RETRY_OPTIONS.delayMs;
170027
- const message = parseAndFormatApiError(error40 instanceof Error ? error40.message : String(error40));
170028
- debugLogger23.warn(`Rate limit throttling detected (retry ${rateLimitRetryCount}/${maxRateLimitRetries}). Waiting ${delayMs / 1e3}s before retrying...`);
170029
- yield {
170030
- type: StreamEventType.RETRY,
170031
- retryInfo: {
170032
- message,
170033
- attempt: rateLimitRetryCount,
170034
- maxRetries: maxRateLimitRetries,
170035
- delayMs
170036
- }
170037
- };
170038
- attempt--;
170039
- await new Promise((res) => setTimeout(res, delayMs));
170040
- continue;
170041
- }
170042
- const isTransientStreamError = error40 instanceof InvalidStreamError;
170043
- if (isTransientStreamError && invalidStreamRetryCount < INVALID_STREAM_RETRY_CONFIG.maxRetries) {
170044
- invalidStreamRetryCount++;
170045
- const delayMs = INVALID_STREAM_RETRY_CONFIG.initialDelayMs * invalidStreamRetryCount;
170046
- debugLogger23.warn(`Invalid stream [${error40.type}] (retry ${invalidStreamRetryCount}/${INVALID_STREAM_RETRY_CONFIG.maxRetries}). Waiting ${delayMs / 1e3}s before retrying...`);
170047
- logContentRetry(self2.config, new ContentRetryEvent(invalidStreamRetryCount - 1, error40.type, delayMs, model));
170048
- yield { type: StreamEventType.RETRY };
170049
- attempt--;
170050
- await new Promise((res) => setTimeout(res, delayMs));
170051
- continue;
170052
- }
170053
- if (isTransientStreamError && error40.type === "NO_RESPONSE_TEXT") {
170054
- const trimmedContents = trimToolErrorsFromContext(requestContents);
170055
- if (trimmedContents.length < requestContents.length) {
170056
- debugLogger23.warn(`NO_RESPONSE_TEXT: retrying with trimmed context (removed ${requestContents.length - trimmedContents.length} tool-error messages)`);
170057
- try {
170058
- const stream2 = await self2.makeApiCallAndProcessStream(model, trimmedContents, params, prompt_id);
170059
- for await (const chunk of stream2) {
170060
- yield { type: StreamEventType.CHUNK, value: chunk };
170061
- }
170062
- lastError = null;
170063
- } catch {
170064
- }
170065
- }
170066
- break;
170067
- }
170068
- if (isTransientStreamError) {
170069
- break;
170070
- }
170071
- const isContentError = error40 instanceof InvalidStreamError;
170072
- if (isContentError) {
170073
- if (attempt < INVALID_CONTENT_RETRY_OPTIONS.maxAttempts - 1) {
170074
- logContentRetry(self2.config, new ContentRetryEvent(attempt, error40.type, INVALID_CONTENT_RETRY_OPTIONS.initialDelayMs, model));
170075
- await new Promise((res) => setTimeout(res, INVALID_CONTENT_RETRY_OPTIONS.initialDelayMs * (attempt + 1)));
170076
- continue;
170077
- }
170078
- }
170079
- break;
170080
- }
170081
- }
170082
- if (lastError) {
170083
- if (lastError instanceof InvalidStreamError) {
170084
- const totalAttempts = invalidStreamRetryCount + 1;
170085
- logContentRetryFailure(self2.config, new ContentRetryFailureEvent(totalAttempts, lastError.type, model));
170086
- }
170087
- throw lastError;
170088
- }
170089
- } finally {
170090
- streamDoneResolver();
170091
- }
170092
- }();
170093
- }
170094
- async makeApiCallAndProcessStream(model, requestContents, params, prompt_id) {
170095
- const apiCall = /* @__PURE__ */ __name(() => this.config.getContentGenerator().generateContentStream({
170096
- model,
170097
- contents: requestContents,
170098
- config: { ...this.generationConfig, ...params.config }
170099
- }, prompt_id), "apiCall");
170100
- const streamResponse2 = await retryWithBackoff(apiCall, {
170101
- shouldRetryOnError: /* @__PURE__ */ __name((error40) => {
170102
- if (error40 instanceof Error) {
170103
- if (isSchemaDepthError(error40.message))
170104
- return false;
170105
- if (isInvalidArgumentError(error40.message))
170106
- return false;
170107
- }
170108
- const status = getErrorStatus(error40);
170109
- if (status === 400)
170110
- return false;
170111
- if (status === 429)
170112
- return true;
170113
- if (status && status >= 500 && status < 600)
170114
- return true;
170115
- return false;
170116
- }, "shouldRetryOnError")
170117
- });
170118
- return this.processStreamResponse(model, streamResponse2);
170119
- }
170120
- /**
170121
- * Returns the chat history.
170122
- *
170123
- * @remarks
170124
- * The history is a list of contents alternating between user and model.
170125
- *
170126
- * There are two types of history:
170127
- * - The `curated history` contains only the valid turns between user and
170128
- * model, which will be included in the subsequent requests sent to the model.
170129
- * - The `comprehensive history` contains all turns, including invalid or
170130
- * empty model outputs, providing a complete record of the history.
170131
- *
170132
- * The history is updated after receiving the response from the model,
170133
- * for streaming response, it means receiving the last chunk of the response.
170134
- *
170135
- * The `comprehensive history` is returned by default. To get the `curated
170136
- * history`, set the `curated` parameter to `true`.
170137
- *
170138
- * @param curated - whether to return the curated history or the comprehensive
170139
- * history.
170140
- * @return History contents alternating between user and model for the entire
170141
- * chat session.
170142
- */
170143
- getHistory(curated = false) {
170144
- const history = curated ? extractCuratedHistory2(this.history) : this.history;
170145
- return structuredClone(history);
170146
- }
170147
- /**
170148
- * Clears the chat history.
170149
- */
170150
- clearHistory() {
170151
- this.history = [];
170152
- }
170153
- /**
170154
- * Adds a new entry to the chat history.
170155
- */
170156
- addHistory(content) {
170157
- this.history.push(content);
170158
- }
170159
- setHistory(history) {
170160
- this.history = history;
170161
- }
170162
- stripThoughtsFromHistory() {
170163
- this.history = this.history.map((content) => {
170164
- if (!content.parts)
170165
- return content;
170166
- const filteredParts = content.parts.filter((part) => !(part && typeof part === "object" && "thought" in part && part.thought)).map((part) => {
170167
- if (part && typeof part === "object" && "thoughtSignature" in part) {
170168
- const newPart = { ...part };
170169
- delete newPart.thoughtSignature;
170170
- return newPart;
170171
- }
170172
- return part;
170173
- });
170174
- return {
170175
- ...content,
170176
- parts: filteredParts
170177
- };
170178
- }).filter((content) => content.parts && content.parts.length > 0);
170179
- }
170180
- /**
170181
- * Pop all orphaned trailing user entries from chat history.
170182
- * In a valid conversation the last entry is always a model response;
170183
- * any trailing user entries are leftovers from a request that failed.
170184
- */
170185
- stripOrphanedUserEntriesFromHistory() {
170186
- while (this.history.length > 0 && this.history[this.history.length - 1].role === "user") {
170187
- this.history.pop();
170188
- }
170189
- }
170190
- setTools(tools) {
170191
- this.generationConfig.tools = tools;
170192
- }
170193
- /** Returns a shallow copy of the current generation config (for cache param snapshots). */
170194
- getGenerationConfig() {
170195
- return { ...this.generationConfig };
170196
- }
170197
- async maybeIncludeSchemaDepthContext(error40) {
170198
- if (isSchemaDepthError(error40.message) || isInvalidArgumentError(error40.message)) {
170199
- const tools = this.config.getToolRegistry().getAllTools();
170200
- const cyclicSchemaTools = [];
170201
- for (const tool of tools) {
170202
- if (tool.schema.parametersJsonSchema && hasCycleInSchema(tool.schema.parametersJsonSchema) || tool.schema.parameters && hasCycleInSchema(tool.schema.parameters)) {
170203
- cyclicSchemaTools.push(tool.displayName);
170204
- }
170205
- }
170206
- if (cyclicSchemaTools.length > 0) {
170207
- const extraDetails = `
170208
-
170209
- This error was probably caused by cyclic schema references in one of the following tools, try disabling them with excludeTools:
170210
-
170211
- - ` + cyclicSchemaTools.join(`
170212
- - `) + `
170213
- `;
170214
- error40.message += extraDetails;
170215
- }
170216
- }
170217
- }
170218
- async *processStreamResponse(model, streamResponse2) {
170219
- const allModelParts = [];
170220
- let usageMetadata;
170221
- let hasToolCall2 = false;
170222
- let hasFinishReason = false;
170223
- for await (const chunk of streamResponse2) {
170224
- hasFinishReason ||= chunk?.candidates?.some((candidate) => candidate.finishReason) ?? false;
170225
- if (isValidResponse2(chunk)) {
170226
- const content = chunk.candidates?.[0]?.content;
170227
- if (content?.parts) {
170228
- if (content.parts.some((part) => part.functionCall)) {
170229
- hasToolCall2 = true;
170230
- }
170231
- allModelParts.push(...content.parts);
170232
- }
170233
- }
170234
- if (chunk.usageMetadata) {
170235
- usageMetadata = chunk.usageMetadata;
170236
- const lastPromptTokenCount = usageMetadata.totalTokenCount || usageMetadata.promptTokenCount;
170237
- if (lastPromptTokenCount && this.telemetryService) {
170238
- this.telemetryService.setLastPromptTokenCount(lastPromptTokenCount);
170239
- }
170240
- if (usageMetadata.cachedContentTokenCount && this.telemetryService) {
170241
- this.telemetryService.setLastCachedContentTokenCount(usageMetadata.cachedContentTokenCount);
170242
- }
170243
- }
170244
- yield chunk;
170245
- }
170246
- let thoughtContentPart;
170247
- const thoughtText = allModelParts.filter((part) => part.thought).map((part) => part.text).join("").trim();
170248
- if (thoughtText !== "") {
170249
- thoughtContentPart = {
170250
- text: thoughtText,
170251
- thought: true
170252
- };
170253
- const thoughtSignature = allModelParts.filter((part) => part.thoughtSignature && part.thought)?.[0]?.thoughtSignature;
170254
- if (thoughtContentPart && thoughtSignature) {
170255
- thoughtContentPart.thoughtSignature = thoughtSignature;
170256
- }
170257
- }
170258
- const contentParts = allModelParts.filter((part) => !part.thought);
170259
- const consolidatedHistoryParts = [];
170260
- for (const part of contentParts) {
170261
- const lastPart = consolidatedHistoryParts[consolidatedHistoryParts.length - 1];
170262
- if (lastPart?.text && isValidNonThoughtTextPart(lastPart) && isValidNonThoughtTextPart(part)) {
170263
- lastPart.text += part.text;
170264
- } else if (isValidContentPart(part)) {
170265
- consolidatedHistoryParts.push(part);
170266
- }
170267
- }
170268
- const contentText = consolidatedHistoryParts.filter((part) => part.text).map((part) => part.text).join("").trim();
170269
- if (thoughtContentPart || contentText || hasToolCall2 || usageMetadata) {
170270
- const contextWindowSize = this.config.getContentGeneratorConfig()?.contextWindowSize;
170271
- this.chatRecordingService?.recordAssistantTurn({
170272
- model,
170273
- message: [
170274
- ...thoughtContentPart ? [thoughtContentPart] : [],
170275
- ...contentText ? [{ text: contentText }] : [],
170276
- ...hasToolCall2 ? contentParts.filter((part) => part.functionCall).map((part) => ({ functionCall: part.functionCall })) : []
170277
- ],
170278
- tokens: usageMetadata,
170279
- contextWindowSize
170280
- });
170281
- }
170282
- if (!hasToolCall2 && (!hasFinishReason || !contentText)) {
170283
- if (!hasFinishReason) {
170284
- throw new InvalidStreamError("Model stream ended without a finish reason.", "NO_FINISH_REASON");
170285
- } else {
170286
- throw new InvalidStreamError("Model stream ended with empty response text.", "NO_RESPONSE_TEXT");
170287
- }
170288
- }
170289
- this.history.push({
170290
- role: "model",
170291
- parts: [
170292
- ...thoughtContentPart ? [thoughtContentPart] : [],
170293
- ...consolidatedHistoryParts
170294
- ]
170295
- });
170296
- }
170297
- };
170298
- __name(isSchemaDepthError, "isSchemaDepthError");
170299
- __name(isInvalidArgumentError, "isInvalidArgumentError");
170300
- }
170301
- });
170302
-
170303
- // packages/core/dist/src/core/prompts.js
170304
- import path19 from "node:path";
170305
- import fs20 from "node:fs";
170306
- import os6 from "node:os";
170307
- import { execSync as execSync3 } from "node:child_process";
170308
- import process3 from "node:process";
170309
- function assemblePromptSections(sections) {
170310
- const stable = sections.filter((s5) => s5.volatility === "stable" && s5.content);
170311
- const workspace = sections.filter((s5) => s5.volatility === "workspace" && s5.content);
170312
- const run3 = sections.filter((s5) => s5.volatility === "run" && s5.content);
170313
- const stablePart = stable.map((s5) => s5.content).join("\n\n");
170314
- const nonStable = [...workspace, ...run3].map((s5) => s5.content).join("\n\n");
170315
- if (!stablePart)
170316
- return nonStable;
170317
- if (!nonStable)
170318
- return stablePart;
170319
- return `${stablePart}${CACHE_BOUNDARY_SENTINEL}${nonStable}`;
170320
- }
170321
- function resolvePathFromEnv(envVar) {
170322
- const trimmedEnvVar = envVar?.trim();
170323
- if (!trimmedEnvVar) {
170324
- return { isSwitch: false, value: null, isDisabled: false };
170325
- }
170326
- const lowerEnvVar = trimmedEnvVar.toLowerCase();
170327
- if (["0", "false", "1", "true"].includes(lowerEnvVar)) {
170328
- const isDisabled = ["0", "false"].includes(lowerEnvVar);
170329
- return { isSwitch: true, value: lowerEnvVar, isDisabled };
170330
- }
170331
- let customPath = trimmedEnvVar;
170332
- if (customPath.startsWith("~/") || customPath === "~") {
170333
- try {
170334
- const home = os6.homedir();
170335
- if (customPath === "~") {
170336
- customPath = home;
170337
- } else {
170338
- customPath = path19.join(home, customPath.slice(2));
170339
- }
170340
- } catch (error40) {
170341
- debugLogger24.warn(`Could not resolve home directory for path: ${trimmedEnvVar}`, error40);
170342
- return { isSwitch: false, value: null, isDisabled: false };
170343
- }
170344
- }
170345
- return {
170346
- isSwitch: false,
170347
- value: path19.resolve(customPath),
170348
- isDisabled: false
170349
- };
170350
- }
170351
- function getCustomSystemPrompt(customInstruction, userMemory, appendInstruction) {
170352
- let instructionText = "";
170353
- if (typeof customInstruction === "string") {
170354
- instructionText = customInstruction;
170355
- } else if (Array.isArray(customInstruction)) {
170356
- instructionText = customInstruction.map((part) => typeof part === "string" ? part : part.text || "").join("");
170357
- } else if (customInstruction && "parts" in customInstruction) {
170358
- instructionText = customInstruction.parts?.map((part) => typeof part === "string" ? part : part.text || "").join("") || "";
170359
- } else if (customInstruction && "text" in customInstruction) {
170360
- instructionText = customInstruction.text || "";
170361
- }
170362
- const memorySuffix = buildSystemPromptSuffix(userMemory);
170363
- return `${instructionText}${memorySuffix}${buildSystemPromptSuffix(appendInstruction)}`;
170364
- }
170365
- function buildSystemPromptSuffix(text) {
170366
- const trimmed2 = text?.trim();
170367
- return trimmed2 ? `
170368
-
170369
- ---
170370
-
170371
- ${trimmed2}` : "";
170372
- }
170373
- function getCoreSystemPrompt(userMemory, model, appendInstruction, interactive = false) {
170374
- let systemMdEnabled = false;
170375
- let systemMdPath = path19.resolve(path19.join(QWEN_CONFIG_DIR, "system.md"));
170376
- const systemMdResolution = resolvePathFromEnv(process3.env["QWEN_SYSTEM_MD"]);
170377
- if (systemMdResolution.value && !systemMdResolution.isDisabled) {
170378
- systemMdEnabled = true;
170379
- if (!systemMdResolution.isSwitch) {
170380
- systemMdPath = systemMdResolution.value;
170381
- }
170382
- if (!fs20.existsSync(systemMdPath)) {
170383
- throw new Error(`missing system prompt file '${systemMdPath}'`);
170384
- }
170385
- }
170386
- if (systemMdEnabled) {
170387
- const customPrompt = fs20.readFileSync(systemMdPath, "utf8");
170388
- const memorySuffix2 = buildSystemPromptSuffix(userMemory);
170389
- const appendSuffix2 = buildSystemPromptSuffix(appendInstruction);
170390
- const full2 = `${customPrompt}${memorySuffix2}${appendSuffix2}`;
170391
- return { staticPrefix: full2, dynamicSuffix: "", full: full2 };
169555
+ if (systemMdEnabled) {
169556
+ const customPrompt = fs18.readFileSync(systemMdPath, "utf8");
169557
+ const memorySuffix2 = buildSystemPromptSuffix(userMemory);
169558
+ const appendSuffix2 = buildSystemPromptSuffix(appendInstruction);
169559
+ const full2 = `${customPrompt}${memorySuffix2}${appendSuffix2}`;
169560
+ return { staticPrefix: full2, dynamicSuffix: "", full: full2 };
170392
169561
  }
170393
169562
  const staticPrefix = `
170394
169563
  You are proto, an interactive CLI agent built by protoLabs.studio, specializing in software engineering tasks. Your primary goal is to help users safely and efficiently, adhering strictly to the following instructions and utilizing your available tools.
@@ -170637,7 +169806,7 @@ Workspace: ${parts2} files
170637
169806
  ${function() {
170638
169807
  if (!interactive)
170639
169808
  return "";
170640
- if (!fs20.existsSync(path19.join(process3.cwd(), ".beads")))
169809
+ if (!fs18.existsSync(path17.join(process3.cwd(), ".beads")))
170641
169810
  return "";
170642
169811
  return `
170643
169812
  # Beads (cross-session task tracker)
@@ -170673,8 +169842,8 @@ Your core function is efficient and safe assistance. Balance extreme conciseness
170673
169842
  const writeSystemMdResolution = resolvePathFromEnv(process3.env["QWEN_WRITE_SYSTEM_MD"]);
170674
169843
  if (writeSystemMdResolution.value && !writeSystemMdResolution.isDisabled) {
170675
169844
  const writePath = writeSystemMdResolution.isSwitch ? systemMdPath : writeSystemMdResolution.value;
170676
- fs20.mkdirSync(path19.dirname(writePath), { recursive: true });
170677
- fs20.writeFileSync(writePath, basePrompt);
169845
+ fs18.mkdirSync(path17.dirname(writePath), { recursive: true });
169846
+ fs18.writeFileSync(writePath, basePrompt);
170678
169847
  }
170679
169848
  const memorySuffix = userMemory && userMemory.trim().length > 0 ? buildSystemPromptSuffix(userMemory) : "";
170680
169849
  const appendSuffix = buildSystemPromptSuffix(appendInstruction);
@@ -170781,7 +169950,7 @@ function getToolCallExamples(model) {
170781
169950
  case "general":
170782
169951
  return generalToolCallExamples;
170783
169952
  default:
170784
- debugLogger24.warn(`Unknown QWEN_CODE_TOOL_CALL_STYLE value: ${toolCallStyle}. Using model-based detection.`);
169953
+ debugLogger21.warn(`Unknown QWEN_CODE_TOOL_CALL_STYLE value: ${toolCallStyle}. Using model-based detection.`);
170785
169954
  break;
170786
169955
  }
170787
169956
  }
@@ -170834,7 +170003,7 @@ function buildCapabilityManifest(mcpToolsByServer, activeSkills) {
170834
170003
 
170835
170004
  ${sections.join("\n\n")}`;
170836
170005
  }
170837
- var debugLogger24, CACHE_BOUNDARY_SENTINEL, generalToolCallExamples, qwenCoderToolCallExamples, qwenVlToolCallExamples, INSIGHT_PROMPTS;
170006
+ var debugLogger21, CACHE_BOUNDARY_SENTINEL, generalToolCallExamples, qwenCoderToolCallExamples, qwenVlToolCallExamples, INSIGHT_PROMPTS;
170838
170007
  var init_prompts = __esm({
170839
170008
  "packages/core/dist/src/core/prompts.js"() {
170840
170009
  "use strict";
@@ -170843,7 +170012,7 @@ var init_prompts = __esm({
170843
170012
  init_gitUtils();
170844
170013
  init_memoryTool();
170845
170014
  init_debugLogger();
170846
- debugLogger24 = createDebugLogger("PROMPTS");
170015
+ debugLogger21 = createDebugLogger("PROMPTS");
170847
170016
  CACHE_BOUNDARY_SENTINEL = "\n__CACHE_BOUNDARY__\n";
170848
170017
  __name(assemblePromptSections, "assemblePromptSections");
170849
170018
  __name(resolvePathFromEnv, "resolvePathFromEnv");
@@ -171362,320 +170531,6 @@ Call respond_in_schema function with A VALID JSON OBJECT as argument:
171362
170531
  }
171363
170532
  });
171364
170533
 
171365
- // packages/core/dist/src/backgroundShells/notifications.js
171366
- function escapeXml(text) {
171367
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
171368
- }
171369
- function buildBackgroundTaskNotification(task) {
171370
- const exitLine = task.exitCode !== void 0 && task.exitCode !== null ? `
171371
- <exit_code>${task.exitCode}</exit_code>` : "";
171372
- const summary = (() => {
171373
- const desc2 = task.description || task.command;
171374
- switch (task.status) {
171375
- case "completed":
171376
- return `Background command "${desc2}" completed${task.exitCode != null ? ` (exit code ${task.exitCode})` : ""}.`;
171377
- case "failed":
171378
- return `Background command "${desc2}" failed${task.exitCode != null ? ` with exit code ${task.exitCode}` : ""}.`;
171379
- case "killed":
171380
- return `Background command "${desc2}" was stopped.`;
171381
- default:
171382
- return `Background command "${desc2}" ended in unknown state.`;
171383
- }
171384
- })();
171385
- return [
171386
- "<task_notification>",
171387
- `<task_id>${task.id}</task_id>`,
171388
- `<output_file>${task.outputPath}</output_file>`,
171389
- `<status>${task.status}</status>${exitLine}`,
171390
- `<summary>${escapeXml(summary)}</summary>`,
171391
- "</task_notification>",
171392
- "",
171393
- `Read ${task.outputPath} to see the full output.`
171394
- ].join("\n");
171395
- }
171396
- var init_notifications = __esm({
171397
- "packages/core/dist/src/backgroundShells/notifications.js"() {
171398
- "use strict";
171399
- init_esbuild_shims();
171400
- __name(escapeXml, "escapeXml");
171401
- __name(buildBackgroundTaskNotification, "buildBackgroundTaskNotification");
171402
- }
171403
- });
171404
-
171405
- // packages/core/dist/src/utils/thoughtUtils.js
171406
- function parseThought(rawText) {
171407
- const startIndex = rawText.indexOf(START_DELIMITER);
171408
- if (startIndex === -1) {
171409
- return { subject: "", description: rawText };
171410
- }
171411
- const endIndex = rawText.indexOf(END_DELIMITER, startIndex + START_DELIMITER.length);
171412
- if (endIndex === -1) {
171413
- return { subject: "", description: rawText };
171414
- }
171415
- const subject = rawText.substring(startIndex + START_DELIMITER.length, endIndex).trim();
171416
- const description = (rawText.substring(0, startIndex) + rawText.substring(endIndex + END_DELIMITER.length)).trim();
171417
- return { subject, description };
171418
- }
171419
- function getThoughtText(response) {
171420
- if (response.candidates && response.candidates.length > 0) {
171421
- const candidate = response.candidates[0];
171422
- if (candidate.content && candidate.content.parts && candidate.content.parts.length > 0) {
171423
- return candidate.content.parts.filter((part) => part.thought && !isInternalPart(part)).map((part) => part.text ?? "").join("");
171424
- }
171425
- }
171426
- return null;
171427
- }
171428
- var START_DELIMITER, END_DELIMITER;
171429
- var init_thoughtUtils = __esm({
171430
- "packages/core/dist/src/utils/thoughtUtils.js"() {
171431
- "use strict";
171432
- init_esbuild_shims();
171433
- init_partUtils();
171434
- START_DELIMITER = "**";
171435
- END_DELIMITER = "**";
171436
- __name(parseThought, "parseThought");
171437
- __name(getThoughtText, "getThoughtText");
171438
- }
171439
- });
171440
-
171441
- // packages/core/dist/src/utils/streamStall.js
171442
- async function* withChunkTimeout(source2, timeoutMs) {
171443
- while (true) {
171444
- let timer;
171445
- const stallPromise = new Promise((_2, reject) => {
171446
- timer = setTimeout(() => reject(new StreamStallError(timeoutMs)), timeoutMs);
171447
- });
171448
- let result;
171449
- try {
171450
- result = await Promise.race([source2.next(), stallPromise]);
171451
- clearTimeout(timer);
171452
- } catch (err3) {
171453
- clearTimeout(timer);
171454
- try {
171455
- void source2.return?.(void 0);
171456
- } catch {
171457
- }
171458
- throw err3;
171459
- }
171460
- if (result.done)
171461
- return;
171462
- yield result.value;
171463
- }
171464
- }
171465
- var StreamStallError;
171466
- var init_streamStall = __esm({
171467
- "packages/core/dist/src/utils/streamStall.js"() {
171468
- "use strict";
171469
- init_esbuild_shims();
171470
- StreamStallError = class extends Error {
171471
- static {
171472
- __name(this, "StreamStallError");
171473
- }
171474
- timeoutMs;
171475
- constructor(timeoutMs) {
171476
- super(`Stream stalled: no data received for ${timeoutMs / 1e3}s. The model connection may have dropped \u2014 please try again.`);
171477
- this.name = "StreamStallError";
171478
- this.timeoutMs = timeoutMs;
171479
- }
171480
- };
171481
- __name(withChunkTimeout, "withChunkTimeout");
171482
- }
171483
- });
171484
-
171485
- // packages/core/dist/src/core/turn.js
171486
- function getCitations(resp) {
171487
- return (resp.candidates?.[0]?.citationMetadata?.citations ?? []).filter((citation) => citation.uri !== void 0).map((citation) => {
171488
- if (citation.title) {
171489
- return `(${citation.title}) ${citation.uri}`;
171490
- }
171491
- return citation.uri;
171492
- });
171493
- }
171494
- var STREAM_STALL_TIMEOUT_MS, GeminiEventType, CompressionStatus, Turn;
171495
- var init_turn = __esm({
171496
- "packages/core/dist/src/core/turn.js"() {
171497
- "use strict";
171498
- init_esbuild_shims();
171499
- init_node();
171500
- init_partUtils();
171501
- init_errorReporting();
171502
- init_errors();
171503
- init_thoughtUtils();
171504
- init_streamStall();
171505
- STREAM_STALL_TIMEOUT_MS = parseInt(process.env["PROTO_STREAM_STALL_TIMEOUT_MS"] ?? "300000", 10);
171506
- (function(GeminiEventType2) {
171507
- GeminiEventType2["Content"] = "content";
171508
- GeminiEventType2["ToolCallRequest"] = "tool_call_request";
171509
- GeminiEventType2["ToolCallResponse"] = "tool_call_response";
171510
- GeminiEventType2["ToolCallConfirmation"] = "tool_call_confirmation";
171511
- GeminiEventType2["UserCancelled"] = "user_cancelled";
171512
- GeminiEventType2["Error"] = "error";
171513
- GeminiEventType2["ChatCompressed"] = "chat_compressed";
171514
- GeminiEventType2["Thought"] = "thought";
171515
- GeminiEventType2["MaxSessionTurns"] = "max_session_turns";
171516
- GeminiEventType2["SessionTokenLimitExceeded"] = "session_token_limit_exceeded";
171517
- GeminiEventType2["Finished"] = "finished";
171518
- GeminiEventType2["LoopDetected"] = "loop_detected";
171519
- GeminiEventType2["Citation"] = "citation";
171520
- GeminiEventType2["Retry"] = "retry";
171521
- GeminiEventType2["HookSystemMessage"] = "hook_system_message";
171522
- })(GeminiEventType || (GeminiEventType = {}));
171523
- (function(CompressionStatus2) {
171524
- CompressionStatus2[CompressionStatus2["COMPRESSED"] = 1] = "COMPRESSED";
171525
- CompressionStatus2[CompressionStatus2["COMPRESSION_FAILED_INFLATED_TOKEN_COUNT"] = 2] = "COMPRESSION_FAILED_INFLATED_TOKEN_COUNT";
171526
- CompressionStatus2[CompressionStatus2["COMPRESSION_FAILED_TOKEN_COUNT_ERROR"] = 3] = "COMPRESSION_FAILED_TOKEN_COUNT_ERROR";
171527
- CompressionStatus2[CompressionStatus2["COMPRESSION_FAILED_EMPTY_SUMMARY"] = 4] = "COMPRESSION_FAILED_EMPTY_SUMMARY";
171528
- CompressionStatus2[CompressionStatus2["NOOP"] = 5] = "NOOP";
171529
- })(CompressionStatus || (CompressionStatus = {}));
171530
- Turn = class {
171531
- static {
171532
- __name(this, "Turn");
171533
- }
171534
- chat;
171535
- prompt_id;
171536
- pendingToolCalls = [];
171537
- debugResponses = [];
171538
- pendingCitations = /* @__PURE__ */ new Set();
171539
- finishReason = void 0;
171540
- currentResponseId;
171541
- constructor(chat, prompt_id) {
171542
- this.chat = chat;
171543
- this.prompt_id = prompt_id;
171544
- }
171545
- // The run method yields simpler events suitable for server logic
171546
- async *run(model, req, signal) {
171547
- try {
171548
- const rawStream = await this.chat.sendMessageStream(model, {
171549
- message: req,
171550
- config: {
171551
- abortSignal: signal
171552
- }
171553
- }, this.prompt_id);
171554
- const responseStream = withChunkTimeout(rawStream, STREAM_STALL_TIMEOUT_MS);
171555
- for await (const streamEvent of responseStream) {
171556
- if (signal?.aborted) {
171557
- yield { type: GeminiEventType.UserCancelled };
171558
- return;
171559
- }
171560
- if (streamEvent.type === "retry") {
171561
- yield {
171562
- type: GeminiEventType.Retry,
171563
- retryInfo: streamEvent.retryInfo
171564
- };
171565
- continue;
171566
- }
171567
- const resp = streamEvent.value;
171568
- if (!resp)
171569
- continue;
171570
- this.debugResponses.push(resp);
171571
- if (resp.responseId) {
171572
- this.currentResponseId = resp.responseId;
171573
- }
171574
- const thoughtText = getThoughtText(resp);
171575
- if (thoughtText) {
171576
- yield {
171577
- type: GeminiEventType.Thought,
171578
- value: parseThought(thoughtText)
171579
- };
171580
- }
171581
- const text = getResponseText(resp);
171582
- if (text) {
171583
- yield { type: GeminiEventType.Content, value: text };
171584
- }
171585
- const functionCalls = resp.functionCalls ?? [];
171586
- for (const fnCall of functionCalls) {
171587
- const event = this.handlePendingFunctionCall(fnCall);
171588
- if (event) {
171589
- yield event;
171590
- }
171591
- }
171592
- for (const citation of getCitations(resp)) {
171593
- this.pendingCitations.add(citation);
171594
- }
171595
- const finishReason = resp.candidates?.[0]?.finishReason;
171596
- if (finishReason) {
171597
- if (finishReason === FinishReason.MAX_TOKENS) {
171598
- for (const tc of this.pendingToolCalls) {
171599
- tc.wasOutputTruncated = true;
171600
- }
171601
- }
171602
- if (this.pendingCitations.size > 0) {
171603
- yield {
171604
- type: GeminiEventType.Citation,
171605
- value: `Citations:
171606
- ${[...this.pendingCitations].sort().join("\n")}`
171607
- };
171608
- this.pendingCitations.clear();
171609
- }
171610
- this.finishReason = finishReason;
171611
- yield {
171612
- type: GeminiEventType.Finished,
171613
- value: {
171614
- reason: finishReason,
171615
- usageMetadata: resp.usageMetadata
171616
- }
171617
- };
171618
- }
171619
- }
171620
- } catch (e4) {
171621
- if (signal.aborted) {
171622
- yield { type: GeminiEventType.UserCancelled };
171623
- return;
171624
- }
171625
- if (e4 instanceof StreamStallError) {
171626
- yield {
171627
- type: GeminiEventType.Error,
171628
- value: {
171629
- error: {
171630
- message: e4.message,
171631
- status: void 0
171632
- }
171633
- }
171634
- };
171635
- return;
171636
- }
171637
- const error40 = toFriendlyError(e4);
171638
- if (error40 instanceof UnauthorizedError) {
171639
- throw error40;
171640
- }
171641
- const contextForReport = [...this.chat.getHistory(
171642
- /*curated*/
171643
- true
171644
- ), req];
171645
- await reportError2(error40, "Error when talking to API", contextForReport, "Turn.run-sendMessageStream");
171646
- const status = typeof error40 === "object" && error40 !== null && "status" in error40 && typeof error40.status === "number" ? error40.status : void 0;
171647
- const structuredError = {
171648
- message: getErrorMessage(error40),
171649
- status
171650
- };
171651
- await this.chat.maybeIncludeSchemaDepthContext(structuredError);
171652
- yield { type: GeminiEventType.Error, value: { error: structuredError } };
171653
- return;
171654
- }
171655
- }
171656
- handlePendingFunctionCall(fnCall) {
171657
- const callId = fnCall.id ?? `${fnCall.name}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
171658
- const name4 = fnCall.name || "undefined_tool_name";
171659
- const args2 = fnCall.args || {};
171660
- const toolCallRequest = {
171661
- callId,
171662
- name: name4,
171663
- args: args2,
171664
- isClientInitiated: false,
171665
- prompt_id: this.prompt_id,
171666
- response_id: this.currentResponseId
171667
- };
171668
- this.pendingToolCalls.push(toolCallRequest);
171669
- return { type: GeminiEventType.ToolCallRequest, value: toolCallRequest };
171670
- }
171671
- getDebugResponses() {
171672
- return this.debugResponses;
171673
- }
171674
- };
171675
- __name(getCitations, "getCitations");
171676
- }
171677
- });
171678
-
171679
170534
  // packages/core/dist/src/hooks/types.js
171680
170535
  function getHookKey(hook) {
171681
170536
  const name4 = hook.name ?? "";
@@ -171710,13 +170565,13 @@ function createHookOutput(eventName, data) {
171710
170565
  return new DefaultHookOutput(data);
171711
170566
  }
171712
170567
  }
171713
- var debugLogger25, HooksConfigSource, HookEventName, HOOKS_CONFIG_FIELDS, HookType, DefaultHookOutput, PreToolUseHookOutput, PostToolUseHookOutput, PostToolUseFailureHookOutput, StopHookOutput, PermissionRequestHookOutput, NotificationType, SessionStartSource, PermissionMode, SessionEndReason, PreCompactTrigger, AgentType;
170568
+ var debugLogger22, HooksConfigSource, HookEventName, HOOKS_CONFIG_FIELDS, HookType, DefaultHookOutput, PreToolUseHookOutput, PostToolUseHookOutput, PostToolUseFailureHookOutput, StopHookOutput, PermissionRequestHookOutput, NotificationType, SessionStartSource, PermissionMode, SessionEndReason, PreCompactTrigger, AgentType;
171714
170569
  var init_types6 = __esm({
171715
170570
  "packages/core/dist/src/hooks/types.js"() {
171716
170571
  "use strict";
171717
170572
  init_esbuild_shims();
171718
170573
  init_debugLogger();
171719
- debugLogger25 = createDebugLogger("TRUSTED_HOOKS");
170574
+ debugLogger22 = createDebugLogger("TRUSTED_HOOKS");
171720
170575
  (function(HooksConfigSource2) {
171721
170576
  HooksConfigSource2["Project"] = "project";
171722
170577
  HooksConfigSource2["User"] = "user";
@@ -171888,10 +170743,10 @@ var init_types6 = __esm({
171888
170743
  this.decision = data.decision ?? "allow";
171889
170744
  this.reason = data.reason ?? "No reason provided";
171890
170745
  if (data.decision === void 0) {
171891
- debugLogger25.debug('PostToolUseHookOutput: No explicit decision set, defaulting to "allow"');
170746
+ debugLogger22.debug('PostToolUseHookOutput: No explicit decision set, defaulting to "allow"');
171892
170747
  }
171893
170748
  if (data.reason === void 0) {
171894
- debugLogger25.debug('PostToolUseHookOutput: No explicit reason set, defaulting to "No reason provided"');
170749
+ debugLogger22.debug('PostToolUseHookOutput: No explicit reason set, defaulting to "No reason provided"');
171895
170750
  }
171896
170751
  }
171897
170752
  };
@@ -172026,30 +170881,30 @@ __export(sessionNotes_exports, {
172026
170881
  readSessionNotes: () => readSessionNotes,
172027
170882
  updateSessionNoteSection: () => updateSessionNoteSection
172028
170883
  });
172029
- import * as fs21 from "node:fs/promises";
172030
- import * as path20 from "node:path";
170884
+ import * as fs19 from "node:fs/promises";
170885
+ import * as path18 from "node:path";
172031
170886
  function getSessionNotesPath(projectDir) {
172032
- return path20.join(projectDir, ".proto", SESSION_NOTES_FILENAME);
170887
+ return path18.join(projectDir, ".proto", SESSION_NOTES_FILENAME);
172033
170888
  }
172034
170889
  async function readSessionNotes(projectDir) {
172035
170890
  const filePath = getSessionNotesPath(projectDir);
172036
170891
  try {
172037
- return await fs21.readFile(filePath, "utf-8");
170892
+ return await fs19.readFile(filePath, "utf-8");
172038
170893
  } catch {
172039
170894
  return null;
172040
170895
  }
172041
170896
  }
172042
170897
  async function initSessionNotes(projectDir) {
172043
170898
  const filePath = getSessionNotesPath(projectDir);
172044
- await fs21.mkdir(path20.dirname(filePath), { recursive: true });
172045
- await fs21.writeFile(filePath, SESSION_NOTES_TEMPLATE, "utf-8");
170899
+ await fs19.mkdir(path18.dirname(filePath), { recursive: true });
170900
+ await fs19.writeFile(filePath, SESSION_NOTES_TEMPLATE, "utf-8");
172046
170901
  logger2.debug("Session notes initialized");
172047
170902
  }
172048
170903
  async function updateSessionNoteSection(projectDir, section, content) {
172049
170904
  const filePath = getSessionNotesPath(projectDir);
172050
170905
  let raw2;
172051
170906
  try {
172052
- raw2 = await fs21.readFile(filePath, "utf-8");
170907
+ raw2 = await fs19.readFile(filePath, "utf-8");
172053
170908
  } catch {
172054
170909
  raw2 = SESSION_NOTES_TEMPLATE;
172055
170910
  }
@@ -172072,8 +170927,8 @@ async function updateSessionNoteSection(projectDir, section, content) {
172072
170927
  const updated = `${before}
172073
170928
  ${content.trim()}
172074
170929
  ${after}`;
172075
- await fs21.mkdir(path20.dirname(filePath), { recursive: true });
172076
- await fs21.writeFile(filePath, updated, "utf-8");
170930
+ await fs19.mkdir(path18.dirname(filePath), { recursive: true });
170931
+ await fs19.writeFile(filePath, updated, "utf-8");
172077
170932
  }
172078
170933
  async function clearSessionNotes(projectDir) {
172079
170934
  await initSessionNotes(projectDir);
@@ -172423,494 +171278,1817 @@ function applyMicrocompact(history, verbatimWindowSize = INCREMENTAL_PROTECTED_T
172423
171278
  ...part.functionResponse,
172424
171279
  response: { output: MICROCOMPACT_STUB }
172425
171280
  }
172426
- };
172427
- });
172428
- return { ...msg, parts: newParts };
172429
- });
172430
- return { newHistory, clearedCount };
172431
- }
172432
- function estimateMicrocompactSaving(history, verbatimWindowSize = INCREMENTAL_PROTECTED_TAIL) {
172433
- const original = JSON.stringify(history).length;
172434
- if (original === 0)
172435
- return 0;
172436
- const { newHistory } = applyMicrocompact(history, verbatimWindowSize);
172437
- const compacted = JSON.stringify(newHistory).length;
172438
- return Math.max(0, (original - compacted) / original);
172439
- }
172440
- var MICROCOMPACT_STUB;
172441
- var init_microcompact = __esm({
172442
- "packages/core/dist/src/services/microcompact.js"() {
172443
- "use strict";
172444
- init_esbuild_shims();
172445
- init_chatCompressionService();
172446
- MICROCOMPACT_STUB = "[result cleared \u2014 see session notes for context]";
172447
- __name(applyMicrocompact, "applyMicrocompact");
172448
- __name(estimateMicrocompactSaving, "estimateMicrocompactSaving");
171281
+ };
171282
+ });
171283
+ return { ...msg, parts: newParts };
171284
+ });
171285
+ return { newHistory, clearedCount };
171286
+ }
171287
+ function estimateMicrocompactSaving(history, verbatimWindowSize = INCREMENTAL_PROTECTED_TAIL) {
171288
+ const original = JSON.stringify(history).length;
171289
+ if (original === 0)
171290
+ return 0;
171291
+ const { newHistory } = applyMicrocompact(history, verbatimWindowSize);
171292
+ const compacted = JSON.stringify(newHistory).length;
171293
+ return Math.max(0, (original - compacted) / original);
171294
+ }
171295
+ var MICROCOMPACT_STUB;
171296
+ var init_microcompact = __esm({
171297
+ "packages/core/dist/src/services/microcompact.js"() {
171298
+ "use strict";
171299
+ init_esbuild_shims();
171300
+ init_chatCompressionService();
171301
+ MICROCOMPACT_STUB = "[result cleared \u2014 see session notes for context]";
171302
+ __name(applyMicrocompact, "applyMicrocompact");
171303
+ __name(estimateMicrocompactSaving, "estimateMicrocompactSaving");
171304
+ }
171305
+ });
171306
+
171307
+ // packages/core/dist/src/services/chatCompressionService.js
171308
+ function applyObservationMask(history, verbatimWindowSize = INCREMENTAL_PROTECTED_TAIL) {
171309
+ if (history.length <= verbatimWindowSize)
171310
+ return history;
171311
+ let pairsKept = 0;
171312
+ let cutIndex = history.length;
171313
+ for (let i4 = history.length - 1; i4 >= 0; i4--) {
171314
+ const msg = history[i4];
171315
+ if (msg?.role === "user" && msg.parts?.some((p2) => p2.functionResponse)) {
171316
+ pairsKept++;
171317
+ if (pairsKept >= verbatimWindowSize) {
171318
+ cutIndex = i4;
171319
+ break;
171320
+ }
171321
+ }
171322
+ }
171323
+ if (cutIndex === 0)
171324
+ return history;
171325
+ const maskedCount = history.slice(0, cutIndex).filter((m3) => m3.role === "user" && m3.parts?.some((p2) => p2.functionResponse) || m3.role === "model" && m3.parts?.some((p2) => p2.functionCall)).length;
171326
+ const placeholder = {
171327
+ role: "user",
171328
+ parts: [
171329
+ {
171330
+ text: `[OBSERVATION_MASK: ${maskedCount} tool call/result pairs from earlier in the session have been masked to reduce context. The most recent ${verbatimWindowSize} pairs are preserved verbatim below.]`
171331
+ }
171332
+ ]
171333
+ };
171334
+ return [placeholder, ...history.slice(cutIndex)];
171335
+ }
171336
+ function extractContentText(content) {
171337
+ if (!content.parts || content.parts.length === 0) {
171338
+ return null;
171339
+ }
171340
+ const texts = content.parts.filter((part) => part.text !== void 0).map((part) => part.text);
171341
+ if (texts.length === 0) {
171342
+ return null;
171343
+ }
171344
+ return texts.join("");
171345
+ }
171346
+ function isCompressedMessage(content) {
171347
+ const text = extractContentText(content);
171348
+ return text !== null && text.startsWith(COMPRESSED_CONTEXT_PREFIX);
171349
+ }
171350
+ function findCompressSplitPoint(contents, fraction) {
171351
+ if (fraction <= 0 || fraction >= 1) {
171352
+ throw new Error("Fraction must be between 0 and 1");
171353
+ }
171354
+ const charCounts = contents.map((content) => JSON.stringify(content).length);
171355
+ const totalCharCount = charCounts.reduce((a2, b2) => a2 + b2, 0);
171356
+ const targetCharCount = totalCharCount * fraction;
171357
+ let lastSplitPoint = 0;
171358
+ let cumulativeCharCount = 0;
171359
+ for (let i4 = 0; i4 < contents.length; i4++) {
171360
+ const content = contents[i4];
171361
+ if (content.role === "user" && !content.parts?.some((part) => !!part.functionResponse)) {
171362
+ if (cumulativeCharCount >= targetCharCount) {
171363
+ return i4;
171364
+ }
171365
+ lastSplitPoint = i4;
171366
+ }
171367
+ cumulativeCharCount += charCounts[i4];
171368
+ }
171369
+ const lastContent = contents[contents.length - 1];
171370
+ if (lastContent?.role === "model" && !lastContent?.parts?.some((part) => part.functionCall)) {
171371
+ return contents.length;
171372
+ }
171373
+ if (lastContent?.role === "user" && lastContent?.parts?.some((part) => !!part.functionResponse)) {
171374
+ return contents.length;
171375
+ }
171376
+ return lastSplitPoint;
171377
+ }
171378
+ function estimateHistoryTokens(history) {
171379
+ return Math.ceil(history.reduce((sum, entry) => sum + JSON.stringify(entry).length, 0) / 4);
171380
+ }
171381
+ function adjustIndexForToolPairs(history, startIndex) {
171382
+ let idx = startIndex;
171383
+ while (idx > 0) {
171384
+ const msg = history[idx];
171385
+ if (!msg)
171386
+ break;
171387
+ const isUserWithNoResponse = msg.role === "user" && !(msg.parts ?? []).some((p2) => "functionResponse" in p2);
171388
+ const isModelWithNoCall = msg.role === "model" && !(msg.parts ?? []).some((p2) => "functionCall" in p2);
171389
+ if (isUserWithNoResponse || isModelWithNoCall)
171390
+ break;
171391
+ idx--;
171392
+ }
171393
+ return idx;
171394
+ }
171395
+ var COMPRESSION_TOKEN_THRESHOLD, COMPRESSION_PRESERVE_THRESHOLD, INCREMENTAL_PROTECTED_TAIL, INCREMENTAL_MAX_CHUNK_SIZE, COMPRESSED_CONTEXT_PREFIX, MIN_COMPRESSION_FRACTION, ChatCompressionService;
171396
+ var init_chatCompressionService = __esm({
171397
+ "packages/core/dist/src/services/chatCompressionService.js"() {
171398
+ "use strict";
171399
+ init_esbuild_shims();
171400
+ init_turn();
171401
+ init_uiTelemetry();
171402
+ init_tokenLimits();
171403
+ init_prompts();
171404
+ init_partUtils();
171405
+ init_loggers();
171406
+ init_types4();
171407
+ init_types6();
171408
+ init_sessionNotes();
171409
+ init_sessionMemoryUtils();
171410
+ init_prompts2();
171411
+ init_microcompact();
171412
+ COMPRESSION_TOKEN_THRESHOLD = 0.7;
171413
+ COMPRESSION_PRESERVE_THRESHOLD = 0.3;
171414
+ INCREMENTAL_PROTECTED_TAIL = 10;
171415
+ INCREMENTAL_MAX_CHUNK_SIZE = 20;
171416
+ COMPRESSED_CONTEXT_PREFIX = "[COMPRESSED_CONTEXT]";
171417
+ MIN_COMPRESSION_FRACTION = 0.05;
171418
+ __name(applyObservationMask, "applyObservationMask");
171419
+ __name(extractContentText, "extractContentText");
171420
+ __name(isCompressedMessage, "isCompressedMessage");
171421
+ __name(findCompressSplitPoint, "findCompressSplitPoint");
171422
+ ChatCompressionService = class {
171423
+ static {
171424
+ __name(this, "ChatCompressionService");
171425
+ }
171426
+ async compress(chat, promptId, force, model, config2, hasFailedCompressionAttempt, signal) {
171427
+ const curatedHistory = chat.getHistory(true);
171428
+ const threshold = config2.getChatCompression()?.contextPercentageThreshold ?? COMPRESSION_TOKEN_THRESHOLD;
171429
+ if (curatedHistory.length === 0 || threshold <= 0 || hasFailedCompressionAttempt && !force) {
171430
+ return {
171431
+ newHistory: null,
171432
+ info: {
171433
+ originalTokenCount: 0,
171434
+ newTokenCount: 0,
171435
+ compressionStatus: CompressionStatus.NOOP
171436
+ }
171437
+ };
171438
+ }
171439
+ const originalTokenCount = uiTelemetryService.getLastPromptTokenCount();
171440
+ if (!force) {
171441
+ const contextLimit = config2.getContentGeneratorConfig()?.contextWindowSize ?? DEFAULT_TOKEN_LIMIT;
171442
+ if (originalTokenCount < threshold * contextLimit) {
171443
+ return {
171444
+ newHistory: null,
171445
+ info: {
171446
+ originalTokenCount,
171447
+ newTokenCount: originalTokenCount,
171448
+ compressionStatus: CompressionStatus.NOOP
171449
+ }
171450
+ };
171451
+ }
171452
+ }
171453
+ if (!force) {
171454
+ const smResult = await this.trySessionMemoryCompaction(curatedHistory, originalTokenCount, config2);
171455
+ if (smResult)
171456
+ return smResult;
171457
+ }
171458
+ if (!force) {
171459
+ const saving = estimateMicrocompactSaving(curatedHistory);
171460
+ if (saving >= 0.2) {
171461
+ const { newHistory: microHistory } = applyMicrocompact(curatedHistory);
171462
+ const newTokenCount = Math.round(originalTokenCount * (1 - saving));
171463
+ uiTelemetryService.setLastPromptTokenCount(newTokenCount);
171464
+ return {
171465
+ newHistory: microHistory,
171466
+ info: {
171467
+ originalTokenCount,
171468
+ newTokenCount,
171469
+ compressionStatus: CompressionStatus.COMPRESSED
171470
+ }
171471
+ };
171472
+ }
171473
+ }
171474
+ const hookSystem = config2.getHookSystem();
171475
+ if (hookSystem) {
171476
+ const trigger = force ? PreCompactTrigger.Manual : PreCompactTrigger.Auto;
171477
+ try {
171478
+ await hookSystem.firePreCompactEvent(trigger, "", signal);
171479
+ } catch (err3) {
171480
+ config2.getDebugLogger().warn(`PreCompact hook failed: ${err3}`);
171481
+ }
171482
+ }
171483
+ const incrementalResult = await this.compressIncremental(curatedHistory, model, config2, promptId, signal);
171484
+ if (incrementalResult.compressed) {
171485
+ return this.finalizeCompression(incrementalResult.newHistory, originalTokenCount, incrementalResult.compressionInputTokenCount, incrementalResult.compressionOutputTokenCount, model, config2, signal);
171486
+ }
171487
+ return this.compressFull(curatedHistory, originalTokenCount, model, config2, promptId, force, signal);
171488
+ }
171489
+ /**
171490
+ * Incremental compression: protects the most recent messages and compresses
171491
+ * the oldest uncompressed chunk. Each call compresses one chunk, making the
171492
+ * operation idempotent and safe to call repeatedly.
171493
+ */
171494
+ async compressIncremental(history, model, config2, promptId, _signal) {
171495
+ const protectedTailMessages = INCREMENTAL_PROTECTED_TAIL;
171496
+ const maxChunkSize = INCREMENTAL_MAX_CHUNK_SIZE;
171497
+ if (history.length <= protectedTailMessages + 2) {
171498
+ return { newHistory: history, compressed: false };
171499
+ }
171500
+ const compressibleEnd = history.length - protectedTailMessages;
171501
+ let chunkStart = 0;
171502
+ while (chunkStart < compressibleEnd) {
171503
+ if (isCompressedMessage(history[chunkStart])) {
171504
+ chunkStart++;
171505
+ } else {
171506
+ break;
171507
+ }
171508
+ }
171509
+ if (chunkStart >= compressibleEnd) {
171510
+ return { newHistory: history, compressed: false };
171511
+ }
171512
+ const chunkEnd = Math.min(chunkStart + maxChunkSize, compressibleEnd);
171513
+ const chunk = history.slice(chunkStart, chunkEnd);
171514
+ const summaryResponse = await config2.getContentGenerator().generateContent({
171515
+ model,
171516
+ contents: [
171517
+ ...chunk,
171518
+ {
171519
+ role: "user",
171520
+ parts: [
171521
+ {
171522
+ text: "Compress the conversation chunk above into a dense summary following the output format."
171523
+ }
171524
+ ]
171525
+ }
171526
+ ],
171527
+ config: {
171528
+ systemInstruction: getIncrementalCompressionPrompt()
171529
+ }
171530
+ }, promptId);
171531
+ const summary = getResponseText(summaryResponse) ?? "";
171532
+ if (!summary || summary.trim().length === 0) {
171533
+ return { newHistory: history, compressed: false };
171534
+ }
171535
+ const prefixedSummary = summary.startsWith(COMPRESSED_CONTEXT_PREFIX) ? summary : `${COMPRESSED_CONTEXT_PREFIX}
171536
+ ${summary}`;
171537
+ const usageMetadata = summaryResponse.usageMetadata;
171538
+ const inputTokenCount = usageMetadata?.promptTokenCount;
171539
+ let outputTokenCount = usageMetadata?.candidatesTokenCount;
171540
+ if (outputTokenCount === void 0 && typeof usageMetadata?.totalTokenCount === "number" && typeof inputTokenCount === "number") {
171541
+ outputTokenCount = Math.max(0, usageMetadata.totalTokenCount - inputTokenCount);
171542
+ }
171543
+ const summaryMessage = {
171544
+ role: "user",
171545
+ parts: [{ text: prefixedSummary }]
171546
+ };
171547
+ const summaryAck = {
171548
+ role: "model",
171549
+ parts: [{ text: "Understood. Incremental context loaded." }]
171550
+ };
171551
+ const newHistory = [
171552
+ ...history.slice(0, chunkStart),
171553
+ summaryMessage,
171554
+ summaryAck,
171555
+ ...history.slice(chunkEnd)
171556
+ ];
171557
+ return {
171558
+ newHistory,
171559
+ compressed: true,
171560
+ compressionInputTokenCount: inputTokenCount,
171561
+ compressionOutputTokenCount: outputTokenCount
171562
+ };
171563
+ }
171564
+ /**
171565
+ * Original full-history compression using getCompressionPrompt().
171566
+ * Used as a fallback when history is too short for incremental compression.
171567
+ */
171568
+ async compressFull(curatedHistory, originalTokenCount, model, config2, promptId, force, signal) {
171569
+ const lastMessage = curatedHistory[curatedHistory.length - 1];
171570
+ const hasOrphanedFuncCall = force && lastMessage?.role === "model" && lastMessage.parts?.some((p2) => !!p2.functionCall);
171571
+ const historyForSplit = hasOrphanedFuncCall ? curatedHistory.slice(0, -1) : curatedHistory;
171572
+ const splitPoint = findCompressSplitPoint(historyForSplit, 1 - COMPRESSION_PRESERVE_THRESHOLD);
171573
+ const historyToCompress = historyForSplit.slice(0, splitPoint);
171574
+ const historyToKeep = historyForSplit.slice(splitPoint);
171575
+ if (historyToCompress.length === 0) {
171576
+ return {
171577
+ newHistory: null,
171578
+ info: {
171579
+ originalTokenCount,
171580
+ newTokenCount: originalTokenCount,
171581
+ compressionStatus: CompressionStatus.NOOP
171582
+ }
171583
+ };
171584
+ }
171585
+ const compressCharCount = historyToCompress.reduce((sum, c4) => sum + JSON.stringify(c4).length, 0);
171586
+ const totalCharCount = historyForSplit.reduce((sum, c4) => sum + JSON.stringify(c4).length, 0);
171587
+ if (totalCharCount > 0 && compressCharCount / totalCharCount < MIN_COMPRESSION_FRACTION) {
171588
+ return {
171589
+ newHistory: null,
171590
+ info: {
171591
+ originalTokenCount,
171592
+ newTokenCount: originalTokenCount,
171593
+ compressionStatus: CompressionStatus.NOOP
171594
+ }
171595
+ };
171596
+ }
171597
+ const summaryResponse = await config2.getContentGenerator().generateContent({
171598
+ model,
171599
+ contents: [
171600
+ ...historyToCompress,
171601
+ {
171602
+ role: "user",
171603
+ parts: [
171604
+ {
171605
+ text: "Generate the <summary> now. Be maximally concise \u2014 every token counts."
171606
+ }
171607
+ ]
171608
+ }
171609
+ ],
171610
+ config: {
171611
+ systemInstruction: getCompressionPrompt()
171612
+ }
171613
+ }, promptId);
171614
+ const summary = getResponseText(summaryResponse) ?? "";
171615
+ const prefixedSummary = summary && summary.trim().length > 0 ? summary.startsWith(COMPRESSED_CONTEXT_PREFIX) ? summary : `${COMPRESSED_CONTEXT_PREFIX}
171616
+ ${summary}` : summary;
171617
+ const compressionUsageMetadata = summaryResponse.usageMetadata;
171618
+ const compressionInputTokenCount = compressionUsageMetadata?.promptTokenCount;
171619
+ let compressionOutputTokenCount = compressionUsageMetadata?.candidatesTokenCount;
171620
+ if (compressionOutputTokenCount === void 0 && typeof compressionUsageMetadata?.totalTokenCount === "number" && typeof compressionInputTokenCount === "number") {
171621
+ compressionOutputTokenCount = Math.max(0, compressionUsageMetadata.totalTokenCount - compressionInputTokenCount);
171622
+ }
171623
+ const isSummaryEmpty = !prefixedSummary || prefixedSummary.trim().length === 0;
171624
+ let extraHistory = [];
171625
+ if (!isSummaryEmpty) {
171626
+ extraHistory = [
171627
+ {
171628
+ role: "user",
171629
+ parts: [{ text: prefixedSummary }]
171630
+ },
171631
+ {
171632
+ role: "model",
171633
+ parts: [{ text: "Got it. Thanks for the additional context!" }]
171634
+ },
171635
+ ...historyToKeep
171636
+ ];
171637
+ }
171638
+ return this.finalizeCompression(isSummaryEmpty ? null : extraHistory, originalTokenCount, compressionInputTokenCount, compressionOutputTokenCount, model, config2, signal, isSummaryEmpty);
171639
+ }
171640
+ /**
171641
+ * Shared finalization logic for both incremental and full compression.
171642
+ * Handles token math, telemetry, hook firing, and status determination.
171643
+ */
171644
+ async finalizeCompression(newHistory, originalTokenCount, compressionInputTokenCount, compressionOutputTokenCount, model, config2, signal, isSummaryEmpty = false) {
171645
+ let newTokenCount = originalTokenCount;
171646
+ let canCalculateNewTokenCount = false;
171647
+ if (!isSummaryEmpty && newHistory) {
171648
+ if (typeof compressionInputTokenCount === "number" && compressionInputTokenCount > 0 && typeof compressionOutputTokenCount === "number" && compressionOutputTokenCount > 0) {
171649
+ canCalculateNewTokenCount = true;
171650
+ newTokenCount = Math.max(0, originalTokenCount - (compressionInputTokenCount - 1e3) + compressionOutputTokenCount);
171651
+ }
171652
+ }
171653
+ logChatCompression(config2, makeChatCompressionEvent({
171654
+ tokens_before: originalTokenCount,
171655
+ tokens_after: newTokenCount,
171656
+ compression_input_token_count: compressionInputTokenCount,
171657
+ compression_output_token_count: compressionOutputTokenCount
171658
+ }));
171659
+ if (isSummaryEmpty) {
171660
+ return {
171661
+ newHistory: null,
171662
+ info: {
171663
+ originalTokenCount,
171664
+ newTokenCount: originalTokenCount,
171665
+ compressionStatus: CompressionStatus.COMPRESSION_FAILED_EMPTY_SUMMARY
171666
+ }
171667
+ };
171668
+ } else if (!canCalculateNewTokenCount) {
171669
+ return {
171670
+ newHistory: null,
171671
+ info: {
171672
+ originalTokenCount,
171673
+ newTokenCount: originalTokenCount,
171674
+ compressionStatus: CompressionStatus.COMPRESSION_FAILED_TOKEN_COUNT_ERROR
171675
+ }
171676
+ };
171677
+ } else if (newTokenCount > originalTokenCount) {
171678
+ return {
171679
+ newHistory: null,
171680
+ info: {
171681
+ originalTokenCount,
171682
+ newTokenCount,
171683
+ compressionStatus: CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT
171684
+ }
171685
+ };
171686
+ } else {
171687
+ uiTelemetryService.setLastPromptTokenCount(newTokenCount);
171688
+ try {
171689
+ const permissionMode = String(config2.getApprovalMode());
171690
+ await config2.getHookSystem()?.fireSessionStartEvent(SessionStartSource.Compact, model ?? "", permissionMode, void 0, signal);
171691
+ } catch (err3) {
171692
+ config2.getDebugLogger().warn(`SessionStart hook failed: ${err3}`);
171693
+ }
171694
+ return {
171695
+ newHistory,
171696
+ info: {
171697
+ originalTokenCount,
171698
+ newTokenCount,
171699
+ compressionStatus: CompressionStatus.COMPRESSED
171700
+ }
171701
+ };
171702
+ }
171703
+ }
171704
+ // ---------------------------------------------------------------------------
171705
+ // Session Memory fast-path compaction
171706
+ // ---------------------------------------------------------------------------
171707
+ /**
171708
+ * Attempt to compact using the continuously-maintained session notes file
171709
+ * instead of making a fresh LLM summarisation call.
171710
+ *
171711
+ * Returns null when the notes are absent, empty, or would not meaningfully
171712
+ * reduce the context (≥ 90 % of original tokens), causing the caller to fall
171713
+ * through to the normal LLM compression pipeline.
171714
+ */
171715
+ async trySessionMemoryCompaction(history, originalTokenCount, config2) {
171716
+ try {
171717
+ await waitForExtraction();
171718
+ const projectDir = config2.getProjectRoot();
171719
+ const notes = await readSessionNotes(projectDir);
171720
+ if (!notes || isSessionNotesEmpty(notes))
171721
+ return null;
171722
+ const { truncated, wasTruncated } = truncateNotesForCompact(notes);
171723
+ const summaryText = `[SESSION_MEMORY_SUMMARY]
171724
+ The following is a running summary of this conversation maintained by the session memory agent.
171725
+ ` + (wasTruncated ? `(Some sections were truncated for length. Full notes at ${config2.getProjectRoot()}/.proto/session-notes.md)
171726
+ ` : "") + `
171727
+ ${truncated}`;
171728
+ const MIN_PRESERVED_TOKENS = 1e4;
171729
+ const lastSummarized = getLastSummarizedCursorIndex();
171730
+ let keepFromIndex = lastSummarized >= 0 ? lastSummarized + 1 : history.length;
171731
+ let preservedTokens = estimateHistoryTokens(history.slice(keepFromIndex));
171732
+ while (keepFromIndex > 0 && preservedTokens < MIN_PRESERVED_TOKENS) {
171733
+ keepFromIndex--;
171734
+ preservedTokens += estimateHistoryTokens([history[keepFromIndex]]);
171735
+ }
171736
+ keepFromIndex = adjustIndexForToolPairs(history, keepFromIndex);
171737
+ const preservedTail = history.slice(keepFromIndex);
171738
+ const summaryUserMsg = {
171739
+ role: "user",
171740
+ parts: [{ text: summaryText }]
171741
+ };
171742
+ const summaryAckMsg = {
171743
+ role: "model",
171744
+ parts: [
171745
+ {
171746
+ text: "Understood. I have the session summary and will continue from the current state."
171747
+ }
171748
+ ]
171749
+ };
171750
+ const newHistory = [summaryUserMsg, summaryAckMsg, ...preservedTail];
171751
+ const newTokenCount = estimateHistoryTokens(newHistory);
171752
+ if (newTokenCount >= originalTokenCount * 0.9)
171753
+ return null;
171754
+ return {
171755
+ newHistory,
171756
+ info: {
171757
+ originalTokenCount,
171758
+ newTokenCount,
171759
+ compressionStatus: CompressionStatus.COMPRESSED
171760
+ }
171761
+ };
171762
+ } catch {
171763
+ return null;
171764
+ }
171765
+ }
171766
+ };
171767
+ __name(estimateHistoryTokens, "estimateHistoryTokens");
171768
+ __name(adjustIndexForToolPairs, "adjustIndexForToolPairs");
171769
+ }
171770
+ });
171771
+
171772
+ // node_modules/async-mutex/index.mjs
171773
+ function insertSorted(a2, v2) {
171774
+ const i4 = findIndexFromEnd(a2, (other2) => v2.priority <= other2.priority);
171775
+ a2.splice(i4 + 1, 0, v2);
171776
+ }
171777
+ function findIndexFromEnd(a2, predicate) {
171778
+ for (let i4 = a2.length - 1; i4 >= 0; i4--) {
171779
+ if (predicate(a2[i4])) {
171780
+ return i4;
171781
+ }
171782
+ }
171783
+ return -1;
171784
+ }
171785
+ var E_TIMEOUT, E_ALREADY_LOCKED, E_CANCELED, __awaiter$2, Semaphore, __awaiter$1, Mutex;
171786
+ var init_async_mutex = __esm({
171787
+ "node_modules/async-mutex/index.mjs"() {
171788
+ init_esbuild_shims();
171789
+ E_TIMEOUT = new Error("timeout while waiting for mutex to become available");
171790
+ E_ALREADY_LOCKED = new Error("mutex already locked");
171791
+ E_CANCELED = new Error("request for lock canceled");
171792
+ __awaiter$2 = function(thisArg, _arguments, P2, generator) {
171793
+ function adopt(value) {
171794
+ return value instanceof P2 ? value : new P2(function(resolve37) {
171795
+ resolve37(value);
171796
+ });
171797
+ }
171798
+ __name(adopt, "adopt");
171799
+ return new (P2 || (P2 = Promise))(function(resolve37, reject) {
171800
+ function fulfilled(value) {
171801
+ try {
171802
+ step(generator.next(value));
171803
+ } catch (e4) {
171804
+ reject(e4);
171805
+ }
171806
+ }
171807
+ __name(fulfilled, "fulfilled");
171808
+ function rejected(value) {
171809
+ try {
171810
+ step(generator["throw"](value));
171811
+ } catch (e4) {
171812
+ reject(e4);
171813
+ }
171814
+ }
171815
+ __name(rejected, "rejected");
171816
+ function step(result) {
171817
+ result.done ? resolve37(result.value) : adopt(result.value).then(fulfilled, rejected);
171818
+ }
171819
+ __name(step, "step");
171820
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
171821
+ });
171822
+ };
171823
+ Semaphore = class {
171824
+ static {
171825
+ __name(this, "Semaphore");
171826
+ }
171827
+ constructor(_value, _cancelError = E_CANCELED) {
171828
+ this._value = _value;
171829
+ this._cancelError = _cancelError;
171830
+ this._queue = [];
171831
+ this._weightedWaiters = [];
171832
+ }
171833
+ acquire(weight = 1, priority = 0) {
171834
+ if (weight <= 0)
171835
+ throw new Error(`invalid weight ${weight}: must be positive`);
171836
+ return new Promise((resolve37, reject) => {
171837
+ const task = { resolve: resolve37, reject, weight, priority };
171838
+ const i4 = findIndexFromEnd(this._queue, (other2) => priority <= other2.priority);
171839
+ if (i4 === -1 && weight <= this._value) {
171840
+ this._dispatchItem(task);
171841
+ } else {
171842
+ this._queue.splice(i4 + 1, 0, task);
171843
+ }
171844
+ });
171845
+ }
171846
+ runExclusive(callback_1) {
171847
+ return __awaiter$2(this, arguments, void 0, function* (callback, weight = 1, priority = 0) {
171848
+ const [value, release2] = yield this.acquire(weight, priority);
171849
+ try {
171850
+ return yield callback(value);
171851
+ } finally {
171852
+ release2();
171853
+ }
171854
+ });
171855
+ }
171856
+ waitForUnlock(weight = 1, priority = 0) {
171857
+ if (weight <= 0)
171858
+ throw new Error(`invalid weight ${weight}: must be positive`);
171859
+ if (this._couldLockImmediately(weight, priority)) {
171860
+ return Promise.resolve();
171861
+ } else {
171862
+ return new Promise((resolve37) => {
171863
+ if (!this._weightedWaiters[weight - 1])
171864
+ this._weightedWaiters[weight - 1] = [];
171865
+ insertSorted(this._weightedWaiters[weight - 1], { resolve: resolve37, priority });
171866
+ });
171867
+ }
171868
+ }
171869
+ isLocked() {
171870
+ return this._value <= 0;
171871
+ }
171872
+ getValue() {
171873
+ return this._value;
171874
+ }
171875
+ setValue(value) {
171876
+ this._value = value;
171877
+ this._dispatchQueue();
171878
+ }
171879
+ release(weight = 1) {
171880
+ if (weight <= 0)
171881
+ throw new Error(`invalid weight ${weight}: must be positive`);
171882
+ this._value += weight;
171883
+ this._dispatchQueue();
171884
+ }
171885
+ cancel() {
171886
+ this._queue.forEach((entry) => entry.reject(this._cancelError));
171887
+ this._queue = [];
171888
+ }
171889
+ _dispatchQueue() {
171890
+ this._drainUnlockWaiters();
171891
+ while (this._queue.length > 0 && this._queue[0].weight <= this._value) {
171892
+ this._dispatchItem(this._queue.shift());
171893
+ this._drainUnlockWaiters();
171894
+ }
171895
+ }
171896
+ _dispatchItem(item) {
171897
+ const previousValue = this._value;
171898
+ this._value -= item.weight;
171899
+ item.resolve([previousValue, this._newReleaser(item.weight)]);
171900
+ }
171901
+ _newReleaser(weight) {
171902
+ let called = false;
171903
+ return () => {
171904
+ if (called)
171905
+ return;
171906
+ called = true;
171907
+ this.release(weight);
171908
+ };
171909
+ }
171910
+ _drainUnlockWaiters() {
171911
+ if (this._queue.length === 0) {
171912
+ for (let weight = this._value; weight > 0; weight--) {
171913
+ const waiters = this._weightedWaiters[weight - 1];
171914
+ if (!waiters)
171915
+ continue;
171916
+ waiters.forEach((waiter) => waiter.resolve());
171917
+ this._weightedWaiters[weight - 1] = [];
171918
+ }
171919
+ } else {
171920
+ const queuedPriority = this._queue[0].priority;
171921
+ for (let weight = this._value; weight > 0; weight--) {
171922
+ const waiters = this._weightedWaiters[weight - 1];
171923
+ if (!waiters)
171924
+ continue;
171925
+ const i4 = waiters.findIndex((waiter) => waiter.priority <= queuedPriority);
171926
+ (i4 === -1 ? waiters : waiters.splice(0, i4)).forEach((waiter) => waiter.resolve());
171927
+ }
171928
+ }
171929
+ }
171930
+ _couldLockImmediately(weight, priority) {
171931
+ return (this._queue.length === 0 || this._queue[0].priority < priority) && weight <= this._value;
171932
+ }
171933
+ };
171934
+ __name(insertSorted, "insertSorted");
171935
+ __name(findIndexFromEnd, "findIndexFromEnd");
171936
+ __awaiter$1 = function(thisArg, _arguments, P2, generator) {
171937
+ function adopt(value) {
171938
+ return value instanceof P2 ? value : new P2(function(resolve37) {
171939
+ resolve37(value);
171940
+ });
171941
+ }
171942
+ __name(adopt, "adopt");
171943
+ return new (P2 || (P2 = Promise))(function(resolve37, reject) {
171944
+ function fulfilled(value) {
171945
+ try {
171946
+ step(generator.next(value));
171947
+ } catch (e4) {
171948
+ reject(e4);
171949
+ }
171950
+ }
171951
+ __name(fulfilled, "fulfilled");
171952
+ function rejected(value) {
171953
+ try {
171954
+ step(generator["throw"](value));
171955
+ } catch (e4) {
171956
+ reject(e4);
171957
+ }
171958
+ }
171959
+ __name(rejected, "rejected");
171960
+ function step(result) {
171961
+ result.done ? resolve37(result.value) : adopt(result.value).then(fulfilled, rejected);
171962
+ }
171963
+ __name(step, "step");
171964
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
171965
+ });
171966
+ };
171967
+ Mutex = class {
171968
+ static {
171969
+ __name(this, "Mutex");
171970
+ }
171971
+ constructor(cancelError) {
171972
+ this._semaphore = new Semaphore(1, cancelError);
171973
+ }
171974
+ acquire() {
171975
+ return __awaiter$1(this, arguments, void 0, function* (priority = 0) {
171976
+ const [, releaser] = yield this._semaphore.acquire(1, priority);
171977
+ return releaser;
171978
+ });
171979
+ }
171980
+ runExclusive(callback, priority = 0) {
171981
+ return this._semaphore.runExclusive(() => callback(), 1, priority);
171982
+ }
171983
+ isLocked() {
171984
+ return this._semaphore.isLocked();
171985
+ }
171986
+ waitForUnlock(priority = 0) {
171987
+ return this._semaphore.waitForUnlock(1, priority);
171988
+ }
171989
+ release() {
171990
+ if (this._semaphore.isLocked())
171991
+ this._semaphore.release();
171992
+ }
171993
+ cancel() {
171994
+ return this._semaphore.cancel();
171995
+ }
171996
+ };
171997
+ }
171998
+ });
171999
+
172000
+ // packages/core/dist/src/utils/jsonl-utils.js
172001
+ import fs20 from "node:fs";
172002
+ import path19 from "node:path";
172003
+ import readline from "node:readline";
172004
+ function getFileLock(filePath) {
172005
+ if (!fileLocks.has(filePath)) {
172006
+ fileLocks.set(filePath, new Mutex());
172007
+ }
172008
+ return fileLocks.get(filePath);
172009
+ }
172010
+ async function readLines(filePath, count) {
172011
+ try {
172012
+ const fileStream = fs20.createReadStream(filePath);
172013
+ const rl = readline.createInterface({
172014
+ input: fileStream,
172015
+ crlfDelay: Infinity
172016
+ });
172017
+ const results = [];
172018
+ for await (const line of rl) {
172019
+ if (results.length >= count)
172020
+ break;
172021
+ const trimmed2 = line.trim();
172022
+ if (trimmed2.length > 0) {
172023
+ results.push(JSON.parse(trimmed2));
172024
+ }
172025
+ }
172026
+ return results;
172027
+ } catch (error40) {
172028
+ if (error40.code !== "ENOENT") {
172029
+ debugLogger23.error(`Error reading first ${count} lines from ${filePath}:`, error40);
172030
+ }
172031
+ return [];
172032
+ }
172033
+ }
172034
+ async function read(filePath) {
172035
+ try {
172036
+ const fileStream = fs20.createReadStream(filePath);
172037
+ const rl = readline.createInterface({
172038
+ input: fileStream,
172039
+ crlfDelay: Infinity
172040
+ });
172041
+ const results = [];
172042
+ for await (const line of rl) {
172043
+ const trimmed2 = line.trim();
172044
+ if (trimmed2.length > 0) {
172045
+ results.push(JSON.parse(trimmed2));
172046
+ }
172047
+ }
172048
+ return results;
172049
+ } catch (error40) {
172050
+ if (error40.code !== "ENOENT") {
172051
+ debugLogger23.error(`Error reading ${filePath}:`, error40);
172052
+ }
172053
+ return [];
172054
+ }
172055
+ }
172056
+ async function writeLine(filePath, data) {
172057
+ const lock = getFileLock(filePath);
172058
+ await lock.runExclusive(() => {
172059
+ const line = `${JSON.stringify(data)}
172060
+ `;
172061
+ const dir = path19.dirname(filePath);
172062
+ if (!fs20.existsSync(dir)) {
172063
+ fs20.mkdirSync(dir, { recursive: true });
172064
+ }
172065
+ fs20.appendFileSync(filePath, line, "utf8");
172066
+ });
172067
+ }
172068
+ function writeLineSync(filePath, data) {
172069
+ const line = `${JSON.stringify(data)}
172070
+ `;
172071
+ const dir = path19.dirname(filePath);
172072
+ if (!fs20.existsSync(dir)) {
172073
+ fs20.mkdirSync(dir, { recursive: true });
172074
+ }
172075
+ fs20.appendFileSync(filePath, line, "utf8");
172076
+ }
172077
+ function write(filePath, data) {
172078
+ const lines = data.map((item) => JSON.stringify(item)).join("\n");
172079
+ const dir = path19.dirname(filePath);
172080
+ if (!fs20.existsSync(dir)) {
172081
+ fs20.mkdirSync(dir, { recursive: true });
172082
+ }
172083
+ fs20.writeFileSync(filePath, `${lines}
172084
+ `, "utf8");
172085
+ }
172086
+ async function countLines(filePath) {
172087
+ try {
172088
+ const fileStream = fs20.createReadStream(filePath);
172089
+ const rl = readline.createInterface({
172090
+ input: fileStream,
172091
+ crlfDelay: Infinity
172092
+ });
172093
+ let count = 0;
172094
+ for await (const line of rl) {
172095
+ if (line.trim().length > 0) {
172096
+ count++;
172097
+ }
172098
+ }
172099
+ return count;
172100
+ } catch (error40) {
172101
+ if (error40.code !== "ENOENT") {
172102
+ debugLogger23.error(`Error counting lines in ${filePath}:`, error40);
172103
+ }
172104
+ return 0;
172105
+ }
172106
+ }
172107
+ function exists(filePath) {
172108
+ try {
172109
+ const stats = fs20.statSync(filePath);
172110
+ return stats.isFile() && stats.size > 0;
172111
+ } catch {
172112
+ return false;
172113
+ }
172114
+ }
172115
+ var debugLogger23, fileLocks;
172116
+ var init_jsonl_utils = __esm({
172117
+ "packages/core/dist/src/utils/jsonl-utils.js"() {
172118
+ "use strict";
172119
+ init_esbuild_shims();
172120
+ init_async_mutex();
172121
+ init_debugLogger();
172122
+ debugLogger23 = createDebugLogger("JSONL");
172123
+ fileLocks = /* @__PURE__ */ new Map();
172124
+ __name(getFileLock, "getFileLock");
172125
+ __name(readLines, "readLines");
172126
+ __name(read, "read");
172127
+ __name(writeLine, "writeLine");
172128
+ __name(writeLineSync, "writeLineSync");
172129
+ __name(write, "write");
172130
+ __name(countLines, "countLines");
172131
+ __name(exists, "exists");
172132
+ }
172133
+ });
172134
+
172135
+ // packages/core/dist/src/services/chatRecordingService.js
172136
+ import path20 from "node:path";
172137
+ import fs21 from "node:fs";
172138
+ import { randomUUID as randomUUID2 } from "node:crypto";
172139
+ var debugLogger24, ChatRecordingService;
172140
+ var init_chatRecordingService = __esm({
172141
+ "packages/core/dist/src/services/chatRecordingService.js"() {
172142
+ "use strict";
172143
+ init_esbuild_shims();
172144
+ init_config3();
172145
+ init_node();
172146
+ init_jsonl_utils();
172147
+ init_gitUtils();
172148
+ init_debugLogger();
172149
+ debugLogger24 = createDebugLogger("CHAT_RECORDING");
172150
+ ChatRecordingService = class {
172151
+ static {
172152
+ __name(this, "ChatRecordingService");
172153
+ }
172154
+ /** UUID of the last written record in the chain */
172155
+ lastRecordUuid = null;
172156
+ config;
172157
+ /** In-memory cache of the current session's custom title (for re-append on exit) */
172158
+ currentCustomTitle;
172159
+ constructor(config2) {
172160
+ this.config = config2;
172161
+ this.lastRecordUuid = config2.getResumedSessionData()?.lastCompletedUuid ?? null;
172162
+ if (config2.getResumedSessionData()) {
172163
+ try {
172164
+ const sessionService = config2.getSessionService();
172165
+ this.currentCustomTitle = sessionService.getSessionTitle(config2.getSessionId());
172166
+ this.finalize();
172167
+ } catch {
172168
+ }
172169
+ }
172170
+ }
172171
+ /**
172172
+ * Returns the session ID.
172173
+ * @returns The session ID.
172174
+ */
172175
+ getSessionId() {
172176
+ return this.config.getSessionId();
172177
+ }
172178
+ /**
172179
+ * Ensures the chats directory exists, creating it if it doesn't exist.
172180
+ * @returns The path to the chats directory.
172181
+ * @throws Error if the directory cannot be created.
172182
+ */
172183
+ ensureChatsDir() {
172184
+ const projectDir = this.config.storage.getProjectDir();
172185
+ const chatsDir = path20.join(projectDir, "chats");
172186
+ try {
172187
+ fs21.mkdirSync(chatsDir, { recursive: true });
172188
+ } catch {
172189
+ }
172190
+ return chatsDir;
172191
+ }
172192
+ /**
172193
+ * Ensures the conversation file exists, creating it if it doesn't exist.
172194
+ * Uses atomic file creation to avoid race conditions.
172195
+ * @returns The path to the conversation file.
172196
+ * @throws Error if the file cannot be created or accessed.
172197
+ */
172198
+ ensureConversationFile() {
172199
+ const chatsDir = this.ensureChatsDir();
172200
+ const sessionId = this.getSessionId();
172201
+ const safeFilename = `${sessionId}.jsonl`;
172202
+ const conversationFile = path20.join(chatsDir, safeFilename);
172203
+ if (fs21.existsSync(conversationFile)) {
172204
+ return conversationFile;
172205
+ }
172206
+ try {
172207
+ fs21.writeFileSync(conversationFile, "", { flag: "wx", encoding: "utf8" });
172208
+ } catch (error40) {
172209
+ const nodeError = error40;
172210
+ if (nodeError.code !== "EEXIST") {
172211
+ const message = error40 instanceof Error ? error40.message : String(error40);
172212
+ throw new Error(`Failed to create conversation file at ${conversationFile}: ${message}`);
172213
+ }
172214
+ }
172215
+ return conversationFile;
172216
+ }
172217
+ /**
172218
+ * Creates base fields for a ChatRecord.
172219
+ */
172220
+ createBaseRecord(type) {
172221
+ return {
172222
+ uuid: randomUUID2(),
172223
+ parentUuid: this.lastRecordUuid,
172224
+ sessionId: this.getSessionId(),
172225
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
172226
+ type,
172227
+ cwd: this.config.getProjectRoot(),
172228
+ version: this.config.getCliVersion() || "unknown",
172229
+ gitBranch: getGitBranch(this.config.getProjectRoot())
172230
+ };
172231
+ }
172232
+ /**
172233
+ * Appends a record to the session file and updates lastRecordUuid.
172234
+ */
172235
+ appendRecord(record2) {
172236
+ try {
172237
+ const conversationFile = this.ensureConversationFile();
172238
+ writeLineSync(conversationFile, record2);
172239
+ this.lastRecordUuid = record2.uuid;
172240
+ } catch (error40) {
172241
+ debugLogger24.error("Error appending record:", error40);
172242
+ throw error40;
172243
+ }
172244
+ }
172245
+ /**
172246
+ * Records a user message.
172247
+ * Writes immediately to disk.
172248
+ *
172249
+ * @param message The raw PartListUnion object as used with the API
172250
+ */
172251
+ recordUserMessage(message) {
172252
+ try {
172253
+ const record2 = {
172254
+ ...this.createBaseRecord("user"),
172255
+ message: createUserContent(message)
172256
+ };
172257
+ this.appendRecord(record2);
172258
+ } catch (error40) {
172259
+ debugLogger24.error("Error saving user message:", error40);
172260
+ }
172261
+ }
172262
+ /**
172263
+ * Records an assistant turn with all available data.
172264
+ * Writes immediately to disk.
172265
+ *
172266
+ * @param data.message The raw PartListUnion object from the model response
172267
+ * @param data.model The model name
172268
+ * @param data.tokens Token usage statistics
172269
+ * @param data.contextWindowSize Context window size of the model
172270
+ * @param data.toolCallsMetadata Enriched tool call info for UI recovery
172271
+ */
172272
+ recordAssistantTurn(data) {
172273
+ try {
172274
+ const record2 = {
172275
+ ...this.createBaseRecord("assistant"),
172276
+ model: data.model
172277
+ };
172278
+ if (data.message !== void 0) {
172279
+ record2.message = createModelContent(data.message);
172280
+ }
172281
+ if (data.tokens) {
172282
+ record2.usageMetadata = data.tokens;
172283
+ }
172284
+ if (data.contextWindowSize !== void 0) {
172285
+ record2.contextWindowSize = data.contextWindowSize;
172286
+ }
172287
+ this.appendRecord(record2);
172288
+ } catch (error40) {
172289
+ debugLogger24.error("Error saving assistant turn:", error40);
172290
+ }
172291
+ }
172292
+ /**
172293
+ * Records tool results (function responses) sent back to the model.
172294
+ * Writes immediately to disk.
172295
+ *
172296
+ * @param message The raw PartListUnion object with functionResponse parts
172297
+ * @param toolCallResult Optional tool call result info for UI recovery
172298
+ */
172299
+ recordToolResult(message, toolCallResult) {
172300
+ try {
172301
+ const record2 = {
172302
+ ...this.createBaseRecord("tool_result"),
172303
+ message: createUserContent(message)
172304
+ };
172305
+ if (toolCallResult) {
172306
+ if (typeof toolCallResult.resultDisplay === "object" && toolCallResult.resultDisplay !== null && "type" in toolCallResult.resultDisplay && toolCallResult.resultDisplay.type === "task_execution") {
172307
+ const taskResult = toolCallResult.resultDisplay;
172308
+ record2.toolCallResult = {
172309
+ ...toolCallResult,
172310
+ resultDisplay: {
172311
+ ...taskResult,
172312
+ toolCalls: []
172313
+ }
172314
+ };
172315
+ } else {
172316
+ record2.toolCallResult = toolCallResult;
172317
+ }
172318
+ }
172319
+ this.appendRecord(record2);
172320
+ } catch (error40) {
172321
+ debugLogger24.error("Error saving tool result:", error40);
172322
+ }
172323
+ }
172324
+ /**
172325
+ * Records a slash command invocation as a system record. This keeps the model
172326
+ * history clean while allowing resume to replay UI output for commands like
172327
+ * /about.
172328
+ */
172329
+ recordSlashCommand(payload) {
172330
+ try {
172331
+ const record2 = {
172332
+ ...this.createBaseRecord("system"),
172333
+ type: "system",
172334
+ subtype: "slash_command",
172335
+ systemPayload: payload
172336
+ };
172337
+ this.appendRecord(record2);
172338
+ } catch (error40) {
172339
+ debugLogger24.error("Error saving slash command record:", error40);
172340
+ }
172341
+ }
172342
+ /**
172343
+ * Records a chat compression checkpoint as a system record. This keeps the UI
172344
+ * history immutable while allowing resume/continue flows to reconstruct the
172345
+ * compressed model-facing history from the stored snapshot.
172346
+ */
172347
+ recordChatCompression(payload) {
172348
+ try {
172349
+ const record2 = {
172350
+ ...this.createBaseRecord("system"),
172351
+ type: "system",
172352
+ subtype: "chat_compression",
172353
+ systemPayload: payload
172354
+ };
172355
+ this.appendRecord(record2);
172356
+ } catch (error40) {
172357
+ debugLogger24.error("Error saving chat compression record:", error40);
172358
+ }
172359
+ }
172360
+ /**
172361
+ * Records a UI telemetry event for replaying metrics on resume.
172362
+ */
172363
+ recordUiTelemetryEvent(uiEvent) {
172364
+ try {
172365
+ const record2 = {
172366
+ ...this.createBaseRecord("system"),
172367
+ type: "system",
172368
+ subtype: "ui_telemetry",
172369
+ systemPayload: { uiEvent }
172370
+ };
172371
+ this.appendRecord(record2);
172372
+ } catch (error40) {
172373
+ debugLogger24.error("Error saving ui telemetry record:", error40);
172374
+ }
172375
+ }
172376
+ /**
172377
+ * Records a custom title for the session (set via /rename).
172378
+ * Appended as a system record so it persists with the session data.
172379
+ * Also caches the title in memory for re-append on shutdown.
172380
+ *
172381
+ * @returns true if the record was written successfully, false on I/O error.
172382
+ */
172383
+ recordCustomTitle(customTitle) {
172384
+ try {
172385
+ const record2 = {
172386
+ ...this.createBaseRecord("system"),
172387
+ type: "system",
172388
+ subtype: "custom_title",
172389
+ systemPayload: { customTitle }
172390
+ };
172391
+ this.appendRecord(record2);
172392
+ this.currentCustomTitle = customTitle;
172393
+ return true;
172394
+ } catch (error40) {
172395
+ debugLogger24.error("Error saving custom title record:", error40);
172396
+ return false;
172397
+ }
172398
+ }
172399
+ /**
172400
+ * Finalizes the current session by re-appending cached metadata to EOF.
172401
+ *
172402
+ * Call this whenever leaving the current session — whether switching to
172403
+ * another session, shutting down the process, or any other transition.
172404
+ * This single entry point replaces scattered re-append calls and ensures
172405
+ * the custom_title record stays within the last 64KB tail window that
172406
+ * readSessionTitleFromFile() scans.
172407
+ *
172408
+ * Best-effort: errors are logged but never thrown.
172409
+ */
172410
+ finalize() {
172411
+ if (!this.currentCustomTitle) {
172412
+ return;
172413
+ }
172414
+ try {
172415
+ const record2 = {
172416
+ ...this.createBaseRecord("system"),
172417
+ type: "system",
172418
+ subtype: "custom_title",
172419
+ systemPayload: { customTitle: this.currentCustomTitle }
172420
+ };
172421
+ this.appendRecord(record2);
172422
+ } catch (error40) {
172423
+ debugLogger24.error("Error finalizing session metadata:", error40);
172424
+ }
172425
+ }
172426
+ /**
172427
+ * Records @-command metadata as a system record for UI reconstruction.
172428
+ */
172429
+ recordAtCommand(payload) {
172430
+ try {
172431
+ const record2 = {
172432
+ ...this.createBaseRecord("system"),
172433
+ type: "system",
172434
+ subtype: "at_command",
172435
+ systemPayload: payload
172436
+ };
172437
+ this.appendRecord(record2);
172438
+ } catch (error40) {
172439
+ debugLogger24.error("Error saving @-command record:", error40);
172440
+ }
172441
+ }
172442
+ };
172449
172443
  }
172450
172444
  });
172451
172445
 
172452
- // packages/core/dist/src/services/chatCompressionService.js
172453
- function applyObservationMask(history, verbatimWindowSize = INCREMENTAL_PROTECTED_TAIL) {
172454
- if (history.length <= verbatimWindowSize)
172455
- return history;
172456
- let pairsKept = 0;
172457
- let cutIndex = history.length;
172458
- for (let i4 = history.length - 1; i4 >= 0; i4--) {
172459
- const msg = history[i4];
172460
- if (msg?.role === "user" && msg.parts?.some((p2) => p2.functionResponse)) {
172461
- pairsKept++;
172462
- if (pairsKept >= verbatimWindowSize) {
172463
- cutIndex = i4;
172464
- break;
172465
- }
172466
- }
172446
+ // packages/core/dist/src/core/geminiChat.js
172447
+ function isValidResponse2(response) {
172448
+ if (response.usageMetadata) {
172449
+ return true;
172467
172450
  }
172468
- if (cutIndex === 0)
172469
- return history;
172470
- const maskedCount = history.slice(0, cutIndex).filter((m3) => m3.role === "user" && m3.parts?.some((p2) => p2.functionResponse) || m3.role === "model" && m3.parts?.some((p2) => p2.functionCall)).length;
172471
- const placeholder = {
172472
- role: "user",
172473
- parts: [
172474
- {
172475
- text: `[OBSERVATION_MASK: ${maskedCount} tool call/result pairs from earlier in the session have been masked to reduce context. The most recent ${verbatimWindowSize} pairs are preserved verbatim below.]`
172476
- }
172477
- ]
172478
- };
172479
- return [placeholder, ...history.slice(cutIndex)];
172480
- }
172481
- function extractContentText(content) {
172482
- if (!content.parts || content.parts.length === 0) {
172483
- return null;
172451
+ if (response.candidates === void 0 || response.candidates.length === 0) {
172452
+ return false;
172484
172453
  }
172485
- const texts = content.parts.filter((part) => part.text !== void 0).map((part) => part.text);
172486
- if (texts.length === 0) {
172487
- return null;
172454
+ if (response.candidates.some((candidate) => candidate.finishReason)) {
172455
+ return true;
172488
172456
  }
172489
- return texts.join("");
172457
+ const content = response.candidates[0]?.content;
172458
+ return content !== void 0 && isValidContent2(content);
172490
172459
  }
172491
- function isCompressedMessage(content) {
172492
- const text = extractContentText(content);
172493
- return text !== null && text.startsWith(COMPRESSED_CONTEXT_PREFIX);
172460
+ function isValidNonThoughtTextPart(part) {
172461
+ return typeof part.text === "string" && !part.thought && !part.thoughtSignature && // Technically, the model should never generate parts that have text and
172462
+ // any of these but we don't trust them so check anyways.
172463
+ !part.functionCall && !part.functionResponse && !part.inlineData && !part.fileData;
172494
172464
  }
172495
- function findCompressSplitPoint(contents, fraction) {
172496
- if (fraction <= 0 || fraction >= 1) {
172497
- throw new Error("Fraction must be between 0 and 1");
172465
+ function isValidContent2(content) {
172466
+ if (content.parts === void 0 || content.parts.length === 0) {
172467
+ return false;
172498
172468
  }
172499
- const charCounts = contents.map((content) => JSON.stringify(content).length);
172500
- const totalCharCount = charCounts.reduce((a2, b2) => a2 + b2, 0);
172501
- const targetCharCount = totalCharCount * fraction;
172502
- let lastSplitPoint = 0;
172503
- let cumulativeCharCount = 0;
172504
- for (let i4 = 0; i4 < contents.length; i4++) {
172505
- const content = contents[i4];
172506
- if (content.role === "user" && !content.parts?.some((part) => !!part.functionResponse)) {
172507
- if (cumulativeCharCount >= targetCharCount) {
172508
- return i4;
172509
- }
172510
- lastSplitPoint = i4;
172469
+ for (const part of content.parts) {
172470
+ if (part === void 0 || Object.keys(part).length === 0) {
172471
+ return false;
172472
+ }
172473
+ if (!isValidContentPart(part)) {
172474
+ return false;
172511
172475
  }
172512
- cumulativeCharCount += charCounts[i4];
172513
172476
  }
172514
- const lastContent = contents[contents.length - 1];
172515
- if (lastContent?.role === "model" && !lastContent?.parts?.some((part) => part.functionCall)) {
172516
- return contents.length;
172477
+ return true;
172478
+ }
172479
+ function isValidContentPart(part) {
172480
+ const isInvalid = !part.thought && !part.thoughtSignature && part.text !== void 0 && part.text === "" && part.functionCall === void 0;
172481
+ return !isInvalid;
172482
+ }
172483
+ function validateHistory2(history) {
172484
+ for (const content of history) {
172485
+ if (content.role !== "user" && content.role !== "model") {
172486
+ throw new Error(`Role must be user or model, but got ${content.role}.`);
172487
+ }
172517
172488
  }
172518
- if (lastContent?.role === "user" && lastContent?.parts?.some((part) => !!part.functionResponse)) {
172519
- return contents.length;
172489
+ }
172490
+ function extractCuratedHistory2(comprehensiveHistory) {
172491
+ if (comprehensiveHistory === void 0 || comprehensiveHistory.length === 0) {
172492
+ return [];
172520
172493
  }
172521
- return lastSplitPoint;
172494
+ const curatedHistory = [];
172495
+ const length = comprehensiveHistory.length;
172496
+ let i4 = 0;
172497
+ while (i4 < length) {
172498
+ if (comprehensiveHistory[i4].role === "user") {
172499
+ curatedHistory.push(comprehensiveHistory[i4]);
172500
+ i4++;
172501
+ } else {
172502
+ const modelOutput = [];
172503
+ let isValid2 = true;
172504
+ while (i4 < length && comprehensiveHistory[i4].role === "model") {
172505
+ modelOutput.push(comprehensiveHistory[i4]);
172506
+ if (isValid2 && !isValidContent2(comprehensiveHistory[i4])) {
172507
+ isValid2 = false;
172508
+ }
172509
+ i4++;
172510
+ }
172511
+ if (isValid2) {
172512
+ curatedHistory.push(...modelOutput);
172513
+ }
172514
+ }
172515
+ }
172516
+ return curatedHistory;
172522
172517
  }
172523
- function estimateHistoryTokens(history) {
172524
- return Math.ceil(history.reduce((sum, entry) => sum + JSON.stringify(entry).length, 0) / 4);
172518
+ function hasTruncationCascade(contents) {
172519
+ if (contents.length === 0)
172520
+ return false;
172521
+ const last2 = contents[contents.length - 1];
172522
+ if (last2.role !== "user")
172523
+ return false;
172524
+ return last2.parts?.some((p2) => {
172525
+ const err3 = p2.functionResponse?.response?.["error"];
172526
+ return typeof err3 === "string" && err3.includes(TRUNCATION_CASCADE_MARKER);
172527
+ }) ?? false;
172525
172528
  }
172526
- function adjustIndexForToolPairs(history, startIndex) {
172527
- let idx = startIndex;
172528
- while (idx > 0) {
172529
- const msg = history[idx];
172530
- if (!msg)
172529
+ function trimLargeToolResponsesFromContext(contents) {
172530
+ return contents.map((content) => {
172531
+ if (content.role !== "user" || !content.parts)
172532
+ return content;
172533
+ const needsTrim = content.parts.some((p2) => {
172534
+ const out2 = p2.functionResponse?.response?.["output"];
172535
+ return typeof out2 === "string" && out2.length > LARGE_TOOL_RESPONSE_TRIM_CHARS;
172536
+ });
172537
+ if (!needsTrim)
172538
+ return content;
172539
+ return {
172540
+ ...content,
172541
+ parts: content.parts.map((p2) => {
172542
+ const out2 = p2.functionResponse?.response?.["output"];
172543
+ if (typeof out2 !== "string" || out2.length <= LARGE_TOOL_RESPONSE_TRIM_CHARS)
172544
+ return p2;
172545
+ const trimmed2 = out2.slice(0, LARGE_TOOL_RESPONSE_TRIM_CHARS) + `
172546
+
172547
+ [... output trimmed from ${out2.length} to ${LARGE_TOOL_RESPONSE_TRIM_CHARS} chars to reduce context size ...]`;
172548
+ return {
172549
+ ...p2,
172550
+ functionResponse: {
172551
+ ...p2.functionResponse,
172552
+ response: {
172553
+ ...p2.functionResponse.response,
172554
+ output: trimmed2
172555
+ }
172556
+ }
172557
+ };
172558
+ })
172559
+ };
172560
+ });
172561
+ }
172562
+ function trimToolErrorsFromContext(contents, maxTrimPairs = 6) {
172563
+ const trimmed2 = [...contents];
172564
+ let trimmed_count = 0;
172565
+ while (trimmed2.length >= 2 && trimmed_count < maxTrimPairs) {
172566
+ const last2 = trimmed2[trimmed2.length - 1];
172567
+ const prev = trimmed2[trimmed2.length - 2];
172568
+ if (last2.role !== "user")
172531
172569
  break;
172532
- const isUserWithNoResponse = msg.role === "user" && !(msg.parts ?? []).some((p2) => "functionResponse" in p2);
172533
- const isModelWithNoCall = msg.role === "model" && !(msg.parts ?? []).some((p2) => "functionCall" in p2);
172534
- if (isUserWithNoResponse || isModelWithNoCall)
172570
+ const allErrors = last2.parts?.every((p2) => p2.functionResponse !== void 0 && typeof p2.functionResponse.response?.["error"] === "string");
172571
+ if (!allErrors)
172535
172572
  break;
172536
- idx--;
172573
+ if (prev.role !== "model")
172574
+ break;
172575
+ const hasToolCall2 = prev.parts?.some((p2) => p2.functionCall !== void 0);
172576
+ if (!hasToolCall2)
172577
+ break;
172578
+ trimmed2.splice(trimmed2.length - 2, 2);
172579
+ trimmed_count++;
172537
172580
  }
172538
- return idx;
172581
+ return trimmed2;
172539
172582
  }
172540
- var COMPRESSION_TOKEN_THRESHOLD, COMPRESSION_PRESERVE_THRESHOLD, INCREMENTAL_PROTECTED_TAIL, INCREMENTAL_MAX_CHUNK_SIZE, COMPRESSED_CONTEXT_PREFIX, MIN_COMPRESSION_FRACTION, ChatCompressionService;
172541
- var init_chatCompressionService = __esm({
172542
- "packages/core/dist/src/services/chatCompressionService.js"() {
172583
+ function isSchemaDepthError(errorMessage) {
172584
+ return errorMessage.includes("maximum schema depth exceeded");
172585
+ }
172586
+ function isInvalidArgumentError(errorMessage) {
172587
+ return errorMessage.includes("Request contains an invalid argument");
172588
+ }
172589
+ var debugLogger25, StreamEventType, INVALID_CONTENT_RETRY_OPTIONS, INVALID_STREAM_RETRY_CONFIG, RATE_LIMIT_RETRY_OPTIONS, TRUNCATION_CASCADE_MARKER, LARGE_TOOL_RESPONSE_TRIM_CHARS, InvalidStreamError, GeminiChat;
172590
+ var init_geminiChat = __esm({
172591
+ "packages/core/dist/src/core/geminiChat.js"() {
172543
172592
  "use strict";
172544
172593
  init_esbuild_shims();
172594
+ init_node();
172595
+ init_retry();
172596
+ init_errors();
172597
+ init_debugLogger();
172598
+ init_errorParsing();
172599
+ init_rateLimit();
172600
+ init_contextLengthError();
172601
+ init_tools();
172602
+ init_chatCompressionService();
172545
172603
  init_turn();
172546
- init_uiTelemetry();
172547
- init_tokenLimits();
172548
- init_prompts();
172549
- init_partUtils();
172550
172604
  init_loggers();
172605
+ init_chatRecordingService();
172551
172606
  init_types4();
172552
- init_types6();
172553
- init_sessionNotes();
172554
- init_sessionMemoryUtils();
172555
- init_prompts2();
172556
- init_microcompact();
172557
- COMPRESSION_TOKEN_THRESHOLD = 0.7;
172558
- COMPRESSION_PRESERVE_THRESHOLD = 0.3;
172559
- INCREMENTAL_PROTECTED_TAIL = 10;
172560
- INCREMENTAL_MAX_CHUNK_SIZE = 20;
172561
- COMPRESSED_CONTEXT_PREFIX = "[COMPRESSED_CONTEXT]";
172562
- MIN_COMPRESSION_FRACTION = 0.05;
172563
- __name(applyObservationMask, "applyObservationMask");
172564
- __name(extractContentText, "extractContentText");
172565
- __name(isCompressedMessage, "isCompressedMessage");
172566
- __name(findCompressSplitPoint, "findCompressSplitPoint");
172567
- ChatCompressionService = class {
172607
+ debugLogger25 = createDebugLogger("QWEN_CODE_CHAT");
172608
+ (function(StreamEventType2) {
172609
+ StreamEventType2["CHUNK"] = "chunk";
172610
+ StreamEventType2["RETRY"] = "retry";
172611
+ })(StreamEventType || (StreamEventType = {}));
172612
+ INVALID_CONTENT_RETRY_OPTIONS = {
172613
+ maxAttempts: 2,
172614
+ // 1 initial call + 1 retry
172615
+ initialDelayMs: 500
172616
+ };
172617
+ INVALID_STREAM_RETRY_CONFIG = {
172618
+ maxRetries: 2,
172619
+ initialDelayMs: 2e3
172620
+ };
172621
+ RATE_LIMIT_RETRY_OPTIONS = {
172622
+ maxRetries: 10,
172623
+ delayMs: 6e4
172624
+ };
172625
+ __name(isValidResponse2, "isValidResponse");
172626
+ __name(isValidNonThoughtTextPart, "isValidNonThoughtTextPart");
172627
+ __name(isValidContent2, "isValidContent");
172628
+ __name(isValidContentPart, "isValidContentPart");
172629
+ __name(validateHistory2, "validateHistory");
172630
+ __name(extractCuratedHistory2, "extractCuratedHistory");
172631
+ TRUNCATION_CASCADE_MARKER = "truncated due to max_tokens limit";
172632
+ LARGE_TOOL_RESPONSE_TRIM_CHARS = 1e4;
172633
+ __name(hasTruncationCascade, "hasTruncationCascade");
172634
+ __name(trimLargeToolResponsesFromContext, "trimLargeToolResponsesFromContext");
172635
+ __name(trimToolErrorsFromContext, "trimToolErrorsFromContext");
172636
+ InvalidStreamError = class extends Error {
172568
172637
  static {
172569
- __name(this, "ChatCompressionService");
172638
+ __name(this, "InvalidStreamError");
172570
172639
  }
172571
- async compress(chat, promptId, force, model, config2, hasFailedCompressionAttempt, signal) {
172572
- const curatedHistory = chat.getHistory(true);
172573
- const threshold = config2.getChatCompression()?.contextPercentageThreshold ?? COMPRESSION_TOKEN_THRESHOLD;
172574
- if (curatedHistory.length === 0 || threshold <= 0 || hasFailedCompressionAttempt && !force) {
172575
- return {
172576
- newHistory: null,
172577
- info: {
172578
- originalTokenCount: 0,
172579
- newTokenCount: 0,
172580
- compressionStatus: CompressionStatus.NOOP
172581
- }
172582
- };
172583
- }
172584
- const originalTokenCount = uiTelemetryService.getLastPromptTokenCount();
172585
- if (!force) {
172586
- const contextLimit = config2.getContentGeneratorConfig()?.contextWindowSize ?? DEFAULT_TOKEN_LIMIT;
172587
- if (originalTokenCount < threshold * contextLimit) {
172588
- return {
172589
- newHistory: null,
172590
- info: {
172591
- originalTokenCount,
172592
- newTokenCount: originalTokenCount,
172593
- compressionStatus: CompressionStatus.NOOP
172594
- }
172595
- };
172596
- }
172597
- }
172598
- if (!force) {
172599
- const smResult = await this.trySessionMemoryCompaction(curatedHistory, originalTokenCount, config2);
172600
- if (smResult)
172601
- return smResult;
172602
- }
172603
- if (!force) {
172604
- const saving = estimateMicrocompactSaving(curatedHistory);
172605
- if (saving >= 0.2) {
172606
- const { newHistory: microHistory } = applyMicrocompact(curatedHistory);
172607
- const newTokenCount = Math.round(originalTokenCount * (1 - saving));
172608
- uiTelemetryService.setLastPromptTokenCount(newTokenCount);
172609
- return {
172610
- newHistory: microHistory,
172611
- info: {
172612
- originalTokenCount,
172613
- newTokenCount,
172614
- compressionStatus: CompressionStatus.COMPRESSED
172615
- }
172616
- };
172617
- }
172618
- }
172619
- const hookSystem = config2.getHookSystem();
172620
- if (hookSystem) {
172621
- const trigger = force ? PreCompactTrigger.Manual : PreCompactTrigger.Auto;
172622
- try {
172623
- await hookSystem.firePreCompactEvent(trigger, "", signal);
172624
- } catch (err3) {
172625
- config2.getDebugLogger().warn(`PreCompact hook failed: ${err3}`);
172626
- }
172627
- }
172628
- const incrementalResult = await this.compressIncremental(curatedHistory, model, config2, promptId, signal);
172629
- if (incrementalResult.compressed) {
172630
- return this.finalizeCompression(incrementalResult.newHistory, originalTokenCount, incrementalResult.compressionInputTokenCount, incrementalResult.compressionOutputTokenCount, model, config2, signal);
172631
- }
172632
- return this.compressFull(curatedHistory, originalTokenCount, model, config2, promptId, force, signal);
172640
+ type;
172641
+ constructor(message, type) {
172642
+ super(message);
172643
+ this.name = "InvalidStreamError";
172644
+ this.type = type;
172645
+ }
172646
+ };
172647
+ GeminiChat = class {
172648
+ static {
172649
+ __name(this, "GeminiChat");
172633
172650
  }
172651
+ config;
172652
+ generationConfig;
172653
+ history;
172654
+ chatRecordingService;
172655
+ telemetryService;
172656
+ // A promise to represent the current state of the message being sent to the
172657
+ // model.
172658
+ sendPromise = Promise.resolve();
172634
172659
  /**
172635
- * Incremental compression: protects the most recent messages and compresses
172636
- * the oldest uncompressed chunk. Each call compresses one chunk, making the
172637
- * operation idempotent and safe to call repeatedly.
172660
+ * Creates a new GeminiChat instance.
172661
+ *
172662
+ * @param config - The configuration object.
172663
+ * @param generationConfig - Optional generation configuration.
172664
+ * @param history - Optional initial conversation history.
172665
+ * @param chatRecordingService - Optional recording service. If provided, chat
172666
+ * messages will be recorded.
172667
+ * @param telemetryService - Optional UI telemetry service. When provided,
172668
+ * prompt token counts are reported on each API response. Pass `undefined`
172669
+ * for sub-agent chats to avoid overwriting the main agent's context usage.
172638
172670
  */
172639
- async compressIncremental(history, model, config2, promptId, _signal) {
172640
- const protectedTailMessages = INCREMENTAL_PROTECTED_TAIL;
172641
- const maxChunkSize = INCREMENTAL_MAX_CHUNK_SIZE;
172642
- if (history.length <= protectedTailMessages + 2) {
172643
- return { newHistory: history, compressed: false };
172644
- }
172645
- const compressibleEnd = history.length - protectedTailMessages;
172646
- let chunkStart = 0;
172647
- while (chunkStart < compressibleEnd) {
172648
- if (isCompressedMessage(history[chunkStart])) {
172649
- chunkStart++;
172650
- } else {
172651
- break;
172652
- }
172653
- }
172654
- if (chunkStart >= compressibleEnd) {
172655
- return { newHistory: history, compressed: false };
172671
+ constructor(config2, generationConfig = {}, history = [], chatRecordingService, telemetryService) {
172672
+ this.config = config2;
172673
+ this.generationConfig = generationConfig;
172674
+ this.history = history;
172675
+ this.chatRecordingService = chatRecordingService;
172676
+ this.telemetryService = telemetryService;
172677
+ validateHistory2(history);
172678
+ }
172679
+ setSystemInstruction(sysInstr) {
172680
+ this.generationConfig.systemInstruction = sysInstr;
172681
+ }
172682
+ /**
172683
+ * Sends a message to the model and returns the response in chunks.
172684
+ *
172685
+ * @remarks
172686
+ * This method will wait for the previous message to be processed before
172687
+ * sending the next message.
172688
+ *
172689
+ * @see {@link Chat#sendMessage} for non-streaming method.
172690
+ * @param params - parameters for sending the message.
172691
+ * @return The model's response.
172692
+ *
172693
+ * @example
172694
+ * ```ts
172695
+ * const chat = ai.chats.create({model: 'gemini-2.0-flash'});
172696
+ * const response = await chat.sendMessageStream({
172697
+ * message: 'Why is the sky blue?'
172698
+ * });
172699
+ * for await (const chunk of response) {
172700
+ * console.log(chunk.text);
172701
+ * }
172702
+ * ```
172703
+ */
172704
+ async sendMessageStream(model, params, prompt_id) {
172705
+ await this.sendPromise;
172706
+ let streamDoneResolver;
172707
+ const streamDonePromise = new Promise((resolve37) => {
172708
+ streamDoneResolver = resolve37;
172709
+ });
172710
+ this.sendPromise = streamDonePromise;
172711
+ const userContent = createUserContent(params.message);
172712
+ this.history.push(userContent);
172713
+ let requestContents = this.getHistory(true);
172714
+ if (hasTruncationCascade(requestContents)) {
172715
+ const withoutErrors = trimToolErrorsFromContext(requestContents);
172716
+ requestContents = trimLargeToolResponsesFromContext(withoutErrors);
172717
+ debugLogger25.warn(`MAX_TOKENS cascade detected: trimmed context from ${this.getHistory(true).length} to ${requestContents.length} entries and capped large tool responses.`);
172656
172718
  }
172657
- const chunkEnd = Math.min(chunkStart + maxChunkSize, compressibleEnd);
172658
- const chunk = history.slice(chunkStart, chunkEnd);
172659
- const summaryResponse = await config2.getContentGenerator().generateContent({
172660
- model,
172661
- contents: [
172662
- ...chunk,
172663
- {
172664
- role: "user",
172665
- parts: [
172666
- {
172667
- text: "Compress the conversation chunk above into a dense summary following the output format."
172719
+ const self2 = this;
172720
+ return async function* () {
172721
+ try {
172722
+ let lastError = new Error("Request failed after all retries.");
172723
+ let rateLimitRetryCount = 0;
172724
+ let invalidStreamRetryCount = 0;
172725
+ let reactiveCompressionAttempted = false;
172726
+ const cgConfig = self2.config.getContentGeneratorConfig();
172727
+ const maxRateLimitRetries = cgConfig?.maxRetries ?? RATE_LIMIT_RETRY_OPTIONS.maxRetries;
172728
+ const extraRetryErrorCodes = cgConfig?.retryErrorCodes;
172729
+ for (let attempt = 0; attempt < INVALID_CONTENT_RETRY_OPTIONS.maxAttempts; attempt++) {
172730
+ try {
172731
+ if (attempt > 0 || rateLimitRetryCount > 0 || invalidStreamRetryCount > 0) {
172732
+ yield { type: StreamEventType.RETRY };
172668
172733
  }
172669
- ]
172734
+ const stream2 = await self2.makeApiCallAndProcessStream(model, requestContents, params, prompt_id);
172735
+ for await (const chunk of stream2) {
172736
+ yield { type: StreamEventType.CHUNK, value: chunk };
172737
+ }
172738
+ lastError = null;
172739
+ break;
172740
+ } catch (error40) {
172741
+ lastError = error40;
172742
+ const isRateLimit = isRateLimitError(error40, extraRetryErrorCodes);
172743
+ if (isRateLimit && rateLimitRetryCount < maxRateLimitRetries) {
172744
+ rateLimitRetryCount++;
172745
+ const delayMs = RATE_LIMIT_RETRY_OPTIONS.delayMs;
172746
+ const message = parseAndFormatApiError(error40 instanceof Error ? error40.message : String(error40));
172747
+ debugLogger25.warn(`Rate limit throttling detected (retry ${rateLimitRetryCount}/${maxRateLimitRetries}). Waiting ${delayMs / 1e3}s before retrying...`);
172748
+ yield {
172749
+ type: StreamEventType.RETRY,
172750
+ retryInfo: {
172751
+ message,
172752
+ attempt: rateLimitRetryCount,
172753
+ maxRetries: maxRateLimitRetries,
172754
+ delayMs
172755
+ }
172756
+ };
172757
+ attempt--;
172758
+ await new Promise((res) => setTimeout(res, delayMs));
172759
+ continue;
172760
+ }
172761
+ const isTransientStreamError = error40 instanceof InvalidStreamError;
172762
+ if (isTransientStreamError && invalidStreamRetryCount < INVALID_STREAM_RETRY_CONFIG.maxRetries) {
172763
+ invalidStreamRetryCount++;
172764
+ const delayMs = INVALID_STREAM_RETRY_CONFIG.initialDelayMs * invalidStreamRetryCount;
172765
+ debugLogger25.warn(`Invalid stream [${error40.type}] (retry ${invalidStreamRetryCount}/${INVALID_STREAM_RETRY_CONFIG.maxRetries}). Waiting ${delayMs / 1e3}s before retrying...`);
172766
+ logContentRetry(self2.config, new ContentRetryEvent(invalidStreamRetryCount - 1, error40.type, delayMs, model));
172767
+ yield { type: StreamEventType.RETRY };
172768
+ attempt--;
172769
+ await new Promise((res) => setTimeout(res, delayMs));
172770
+ continue;
172771
+ }
172772
+ if (isTransientStreamError && error40.type === "NO_RESPONSE_TEXT") {
172773
+ const trimmedContents = trimToolErrorsFromContext(requestContents);
172774
+ if (trimmedContents.length < requestContents.length) {
172775
+ debugLogger25.warn(`NO_RESPONSE_TEXT: retrying with trimmed context (removed ${requestContents.length - trimmedContents.length} tool-error messages)`);
172776
+ try {
172777
+ const stream2 = await self2.makeApiCallAndProcessStream(model, trimmedContents, params, prompt_id);
172778
+ for await (const chunk of stream2) {
172779
+ yield { type: StreamEventType.CHUNK, value: chunk };
172780
+ }
172781
+ lastError = null;
172782
+ } catch {
172783
+ }
172784
+ }
172785
+ break;
172786
+ }
172787
+ if (isTransientStreamError) {
172788
+ break;
172789
+ }
172790
+ if (!reactiveCompressionAttempted) {
172791
+ const overflow = getContextLengthExceededInfo(error40);
172792
+ if (overflow.isExceeded) {
172793
+ reactiveCompressionAttempted = true;
172794
+ debugLogger25.warn("Context length exceeded; attempting reactive compression and one retry.");
172795
+ try {
172796
+ const { newHistory, info: info3 } = await new ChatCompressionService().compress(
172797
+ self2,
172798
+ prompt_id,
172799
+ true,
172800
+ // force — bypasses the hasFailedCompressionAttempt guard
172801
+ model,
172802
+ self2.config,
172803
+ false,
172804
+ params.config?.abortSignal
172805
+ );
172806
+ if (info3.compressionStatus === CompressionStatus.COMPRESSED && newHistory) {
172807
+ self2.setHistory(newHistory);
172808
+ self2.config.getFileReadCache().clear();
172809
+ self2.telemetryService?.setLastPromptTokenCount(info3.newTokenCount);
172810
+ requestContents = self2.getHistory(true);
172811
+ debugLogger25.info(`Reactive compression succeeded (${info3.originalTokenCount} -> ${info3.newTokenCount} tokens); retrying.`);
172812
+ attempt--;
172813
+ yield { type: StreamEventType.RETRY };
172814
+ continue;
172815
+ }
172816
+ debugLogger25.warn("Reactive compression did not reduce context; giving up.");
172817
+ } catch (compressionError) {
172818
+ debugLogger25.warn(`Reactive compression failed: ${compressionError instanceof Error ? compressionError.message : String(compressionError)}`);
172819
+ }
172820
+ break;
172821
+ }
172822
+ }
172823
+ const isContentError = error40 instanceof InvalidStreamError;
172824
+ if (isContentError) {
172825
+ if (attempt < INVALID_CONTENT_RETRY_OPTIONS.maxAttempts - 1) {
172826
+ logContentRetry(self2.config, new ContentRetryEvent(attempt, error40.type, INVALID_CONTENT_RETRY_OPTIONS.initialDelayMs, model));
172827
+ await new Promise((res) => setTimeout(res, INVALID_CONTENT_RETRY_OPTIONS.initialDelayMs * (attempt + 1)));
172828
+ continue;
172829
+ }
172830
+ }
172831
+ break;
172832
+ }
172670
172833
  }
172671
- ],
172672
- config: {
172673
- systemInstruction: getIncrementalCompressionPrompt()
172834
+ if (lastError) {
172835
+ if (lastError instanceof InvalidStreamError) {
172836
+ const totalAttempts = invalidStreamRetryCount + 1;
172837
+ logContentRetryFailure(self2.config, new ContentRetryFailureEvent(totalAttempts, lastError.type, model));
172838
+ }
172839
+ throw lastError;
172840
+ }
172841
+ } finally {
172842
+ streamDoneResolver();
172674
172843
  }
172675
- }, promptId);
172676
- const summary = getResponseText(summaryResponse) ?? "";
172677
- if (!summary || summary.trim().length === 0) {
172678
- return { newHistory: history, compressed: false };
172679
- }
172680
- const prefixedSummary = summary.startsWith(COMPRESSED_CONTEXT_PREFIX) ? summary : `${COMPRESSED_CONTEXT_PREFIX}
172681
- ${summary}`;
172682
- const usageMetadata = summaryResponse.usageMetadata;
172683
- const inputTokenCount = usageMetadata?.promptTokenCount;
172684
- let outputTokenCount = usageMetadata?.candidatesTokenCount;
172685
- if (outputTokenCount === void 0 && typeof usageMetadata?.totalTokenCount === "number" && typeof inputTokenCount === "number") {
172686
- outputTokenCount = Math.max(0, usageMetadata.totalTokenCount - inputTokenCount);
172687
- }
172688
- const summaryMessage = {
172689
- role: "user",
172690
- parts: [{ text: prefixedSummary }]
172691
- };
172692
- const summaryAck = {
172693
- role: "model",
172694
- parts: [{ text: "Understood. Incremental context loaded." }]
172695
- };
172696
- const newHistory = [
172697
- ...history.slice(0, chunkStart),
172698
- summaryMessage,
172699
- summaryAck,
172700
- ...history.slice(chunkEnd)
172701
- ];
172702
- return {
172703
- newHistory,
172704
- compressed: true,
172705
- compressionInputTokenCount: inputTokenCount,
172706
- compressionOutputTokenCount: outputTokenCount
172707
- };
172844
+ }();
172845
+ }
172846
+ async makeApiCallAndProcessStream(model, requestContents, params, prompt_id) {
172847
+ const apiCall = /* @__PURE__ */ __name(() => this.config.getContentGenerator().generateContentStream({
172848
+ model,
172849
+ contents: requestContents,
172850
+ config: { ...this.generationConfig, ...params.config }
172851
+ }, prompt_id), "apiCall");
172852
+ const streamResponse2 = await retryWithBackoff(apiCall, {
172853
+ shouldRetryOnError: /* @__PURE__ */ __name((error40) => {
172854
+ if (error40 instanceof Error) {
172855
+ if (isSchemaDepthError(error40.message))
172856
+ return false;
172857
+ if (isInvalidArgumentError(error40.message))
172858
+ return false;
172859
+ }
172860
+ const status = getErrorStatus(error40);
172861
+ if (status === 400)
172862
+ return false;
172863
+ if (status === 429)
172864
+ return true;
172865
+ if (status && status >= 500 && status < 600)
172866
+ return true;
172867
+ return false;
172868
+ }, "shouldRetryOnError")
172869
+ });
172870
+ return this.processStreamResponse(model, streamResponse2);
172708
172871
  }
172709
172872
  /**
172710
- * Original full-history compression using getCompressionPrompt().
172711
- * Used as a fallback when history is too short for incremental compression.
172873
+ * Returns the chat history.
172874
+ *
172875
+ * @remarks
172876
+ * The history is a list of contents alternating between user and model.
172877
+ *
172878
+ * There are two types of history:
172879
+ * - The `curated history` contains only the valid turns between user and
172880
+ * model, which will be included in the subsequent requests sent to the model.
172881
+ * - The `comprehensive history` contains all turns, including invalid or
172882
+ * empty model outputs, providing a complete record of the history.
172883
+ *
172884
+ * The history is updated after receiving the response from the model,
172885
+ * for streaming response, it means receiving the last chunk of the response.
172886
+ *
172887
+ * The `comprehensive history` is returned by default. To get the `curated
172888
+ * history`, set the `curated` parameter to `true`.
172889
+ *
172890
+ * @param curated - whether to return the curated history or the comprehensive
172891
+ * history.
172892
+ * @return History contents alternating between user and model for the entire
172893
+ * chat session.
172712
172894
  */
172713
- async compressFull(curatedHistory, originalTokenCount, model, config2, promptId, force, signal) {
172714
- const lastMessage = curatedHistory[curatedHistory.length - 1];
172715
- const hasOrphanedFuncCall = force && lastMessage?.role === "model" && lastMessage.parts?.some((p2) => !!p2.functionCall);
172716
- const historyForSplit = hasOrphanedFuncCall ? curatedHistory.slice(0, -1) : curatedHistory;
172717
- const splitPoint = findCompressSplitPoint(historyForSplit, 1 - COMPRESSION_PRESERVE_THRESHOLD);
172718
- const historyToCompress = historyForSplit.slice(0, splitPoint);
172719
- const historyToKeep = historyForSplit.slice(splitPoint);
172720
- if (historyToCompress.length === 0) {
172721
- return {
172722
- newHistory: null,
172723
- info: {
172724
- originalTokenCount,
172725
- newTokenCount: originalTokenCount,
172726
- compressionStatus: CompressionStatus.NOOP
172895
+ getHistory(curated = false) {
172896
+ const history = curated ? extractCuratedHistory2(this.history) : this.history;
172897
+ return structuredClone(history);
172898
+ }
172899
+ /**
172900
+ * Clears the chat history.
172901
+ */
172902
+ clearHistory() {
172903
+ this.history = [];
172904
+ }
172905
+ /**
172906
+ * Adds a new entry to the chat history.
172907
+ */
172908
+ addHistory(content) {
172909
+ this.history.push(content);
172910
+ }
172911
+ setHistory(history) {
172912
+ this.history = history;
172913
+ }
172914
+ stripThoughtsFromHistory() {
172915
+ this.history = this.history.map((content) => {
172916
+ if (!content.parts)
172917
+ return content;
172918
+ const filteredParts = content.parts.filter((part) => !(part && typeof part === "object" && "thought" in part && part.thought)).map((part) => {
172919
+ if (part && typeof part === "object" && "thoughtSignature" in part) {
172920
+ const newPart = { ...part };
172921
+ delete newPart.thoughtSignature;
172922
+ return newPart;
172727
172923
  }
172728
- };
172729
- }
172730
- const compressCharCount = historyToCompress.reduce((sum, c4) => sum + JSON.stringify(c4).length, 0);
172731
- const totalCharCount = historyForSplit.reduce((sum, c4) => sum + JSON.stringify(c4).length, 0);
172732
- if (totalCharCount > 0 && compressCharCount / totalCharCount < MIN_COMPRESSION_FRACTION) {
172924
+ return part;
172925
+ });
172733
172926
  return {
172734
- newHistory: null,
172735
- info: {
172736
- originalTokenCount,
172737
- newTokenCount: originalTokenCount,
172738
- compressionStatus: CompressionStatus.NOOP
172739
- }
172927
+ ...content,
172928
+ parts: filteredParts
172740
172929
  };
172741
- }
172742
- const summaryResponse = await config2.getContentGenerator().generateContent({
172743
- model,
172744
- contents: [
172745
- ...historyToCompress,
172746
- {
172747
- role: "user",
172748
- parts: [
172749
- {
172750
- text: "Generate the <summary> now. Be maximally concise \u2014 every token counts."
172751
- }
172752
- ]
172753
- }
172754
- ],
172755
- config: {
172756
- systemInstruction: getCompressionPrompt()
172757
- }
172758
- }, promptId);
172759
- const summary = getResponseText(summaryResponse) ?? "";
172760
- const prefixedSummary = summary && summary.trim().length > 0 ? summary.startsWith(COMPRESSED_CONTEXT_PREFIX) ? summary : `${COMPRESSED_CONTEXT_PREFIX}
172761
- ${summary}` : summary;
172762
- const compressionUsageMetadata = summaryResponse.usageMetadata;
172763
- const compressionInputTokenCount = compressionUsageMetadata?.promptTokenCount;
172764
- let compressionOutputTokenCount = compressionUsageMetadata?.candidatesTokenCount;
172765
- if (compressionOutputTokenCount === void 0 && typeof compressionUsageMetadata?.totalTokenCount === "number" && typeof compressionInputTokenCount === "number") {
172766
- compressionOutputTokenCount = Math.max(0, compressionUsageMetadata.totalTokenCount - compressionInputTokenCount);
172767
- }
172768
- const isSummaryEmpty = !prefixedSummary || prefixedSummary.trim().length === 0;
172769
- let extraHistory = [];
172770
- if (!isSummaryEmpty) {
172771
- extraHistory = [
172772
- {
172773
- role: "user",
172774
- parts: [{ text: prefixedSummary }]
172775
- },
172776
- {
172777
- role: "model",
172778
- parts: [{ text: "Got it. Thanks for the additional context!" }]
172779
- },
172780
- ...historyToKeep
172781
- ];
172782
- }
172783
- return this.finalizeCompression(isSummaryEmpty ? null : extraHistory, originalTokenCount, compressionInputTokenCount, compressionOutputTokenCount, model, config2, signal, isSummaryEmpty);
172930
+ }).filter((content) => content.parts && content.parts.length > 0);
172784
172931
  }
172785
172932
  /**
172786
- * Shared finalization logic for both incremental and full compression.
172787
- * Handles token math, telemetry, hook firing, and status determination.
172933
+ * Pop all orphaned trailing user entries from chat history.
172934
+ * In a valid conversation the last entry is always a model response;
172935
+ * any trailing user entries are leftovers from a request that failed.
172788
172936
  */
172789
- async finalizeCompression(newHistory, originalTokenCount, compressionInputTokenCount, compressionOutputTokenCount, model, config2, signal, isSummaryEmpty = false) {
172790
- let newTokenCount = originalTokenCount;
172791
- let canCalculateNewTokenCount = false;
172792
- if (!isSummaryEmpty && newHistory) {
172793
- if (typeof compressionInputTokenCount === "number" && compressionInputTokenCount > 0 && typeof compressionOutputTokenCount === "number" && compressionOutputTokenCount > 0) {
172794
- canCalculateNewTokenCount = true;
172795
- newTokenCount = Math.max(0, originalTokenCount - (compressionInputTokenCount - 1e3) + compressionOutputTokenCount);
172937
+ stripOrphanedUserEntriesFromHistory() {
172938
+ while (this.history.length > 0 && this.history[this.history.length - 1].role === "user") {
172939
+ this.history.pop();
172940
+ }
172941
+ }
172942
+ setTools(tools) {
172943
+ this.generationConfig.tools = tools;
172944
+ }
172945
+ /** Returns a shallow copy of the current generation config (for cache param snapshots). */
172946
+ getGenerationConfig() {
172947
+ return { ...this.generationConfig };
172948
+ }
172949
+ async maybeIncludeSchemaDepthContext(error40) {
172950
+ if (isSchemaDepthError(error40.message) || isInvalidArgumentError(error40.message)) {
172951
+ const tools = this.config.getToolRegistry().getAllTools();
172952
+ const cyclicSchemaTools = [];
172953
+ for (const tool of tools) {
172954
+ if (tool.schema.parametersJsonSchema && hasCycleInSchema(tool.schema.parametersJsonSchema) || tool.schema.parameters && hasCycleInSchema(tool.schema.parameters)) {
172955
+ cyclicSchemaTools.push(tool.displayName);
172956
+ }
172957
+ }
172958
+ if (cyclicSchemaTools.length > 0) {
172959
+ const extraDetails = `
172960
+
172961
+ This error was probably caused by cyclic schema references in one of the following tools, try disabling them with excludeTools:
172962
+
172963
+ - ` + cyclicSchemaTools.join(`
172964
+ - `) + `
172965
+ `;
172966
+ error40.message += extraDetails;
172796
172967
  }
172797
172968
  }
172798
- logChatCompression(config2, makeChatCompressionEvent({
172799
- tokens_before: originalTokenCount,
172800
- tokens_after: newTokenCount,
172801
- compression_input_token_count: compressionInputTokenCount,
172802
- compression_output_token_count: compressionOutputTokenCount
172803
- }));
172804
- if (isSummaryEmpty) {
172805
- return {
172806
- newHistory: null,
172807
- info: {
172808
- originalTokenCount,
172809
- newTokenCount: originalTokenCount,
172810
- compressionStatus: CompressionStatus.COMPRESSION_FAILED_EMPTY_SUMMARY
172969
+ }
172970
+ async *processStreamResponse(model, streamResponse2) {
172971
+ const allModelParts = [];
172972
+ let usageMetadata;
172973
+ let hasToolCall2 = false;
172974
+ let hasFinishReason = false;
172975
+ for await (const chunk of streamResponse2) {
172976
+ hasFinishReason ||= chunk?.candidates?.some((candidate) => candidate.finishReason) ?? false;
172977
+ if (isValidResponse2(chunk)) {
172978
+ const content = chunk.candidates?.[0]?.content;
172979
+ if (content?.parts) {
172980
+ if (content.parts.some((part) => part.functionCall)) {
172981
+ hasToolCall2 = true;
172982
+ }
172983
+ allModelParts.push(...content.parts);
172811
172984
  }
172812
- };
172813
- } else if (!canCalculateNewTokenCount) {
172814
- return {
172815
- newHistory: null,
172816
- info: {
172817
- originalTokenCount,
172818
- newTokenCount: originalTokenCount,
172819
- compressionStatus: CompressionStatus.COMPRESSION_FAILED_TOKEN_COUNT_ERROR
172985
+ }
172986
+ if (chunk.usageMetadata) {
172987
+ usageMetadata = chunk.usageMetadata;
172988
+ const lastPromptTokenCount = usageMetadata.totalTokenCount || usageMetadata.promptTokenCount;
172989
+ if (lastPromptTokenCount && this.telemetryService) {
172990
+ this.telemetryService.setLastPromptTokenCount(lastPromptTokenCount);
172820
172991
  }
172821
- };
172822
- } else if (newTokenCount > originalTokenCount) {
172823
- return {
172824
- newHistory: null,
172825
- info: {
172826
- originalTokenCount,
172827
- newTokenCount,
172828
- compressionStatus: CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT
172992
+ if (usageMetadata.cachedContentTokenCount && this.telemetryService) {
172993
+ this.telemetryService.setLastCachedContentTokenCount(usageMetadata.cachedContentTokenCount);
172829
172994
  }
172830
- };
172831
- } else {
172832
- uiTelemetryService.setLastPromptTokenCount(newTokenCount);
172833
- try {
172834
- const permissionMode = String(config2.getApprovalMode());
172835
- await config2.getHookSystem()?.fireSessionStartEvent(SessionStartSource.Compact, model ?? "", permissionMode, void 0, signal);
172836
- } catch (err3) {
172837
- config2.getDebugLogger().warn(`SessionStart hook failed: ${err3}`);
172838
172995
  }
172839
- return {
172840
- newHistory,
172841
- info: {
172842
- originalTokenCount,
172843
- newTokenCount,
172844
- compressionStatus: CompressionStatus.COMPRESSED
172845
- }
172996
+ yield chunk;
172997
+ }
172998
+ let thoughtContentPart;
172999
+ const thoughtText = allModelParts.filter((part) => part.thought).map((part) => part.text).join("").trim();
173000
+ if (thoughtText !== "") {
173001
+ thoughtContentPart = {
173002
+ text: thoughtText,
173003
+ thought: true
172846
173004
  };
173005
+ const thoughtSignature = allModelParts.filter((part) => part.thoughtSignature && part.thought)?.[0]?.thoughtSignature;
173006
+ if (thoughtContentPart && thoughtSignature) {
173007
+ thoughtContentPart.thoughtSignature = thoughtSignature;
173008
+ }
172847
173009
  }
172848
- }
172849
- // ---------------------------------------------------------------------------
172850
- // Session Memory fast-path compaction
172851
- // ---------------------------------------------------------------------------
172852
- /**
172853
- * Attempt to compact using the continuously-maintained session notes file
172854
- * instead of making a fresh LLM summarisation call.
172855
- *
172856
- * Returns null when the notes are absent, empty, or would not meaningfully
172857
- * reduce the context (≥ 90 % of original tokens), causing the caller to fall
172858
- * through to the normal LLM compression pipeline.
172859
- */
172860
- async trySessionMemoryCompaction(history, originalTokenCount, config2) {
172861
- try {
172862
- await waitForExtraction();
172863
- const projectDir = config2.getProjectRoot();
172864
- const notes = await readSessionNotes(projectDir);
172865
- if (!notes || isSessionNotesEmpty(notes))
172866
- return null;
172867
- const { truncated, wasTruncated } = truncateNotesForCompact(notes);
172868
- const summaryText = `[SESSION_MEMORY_SUMMARY]
172869
- The following is a running summary of this conversation maintained by the session memory agent.
172870
- ` + (wasTruncated ? `(Some sections were truncated for length. Full notes at ${config2.getProjectRoot()}/.proto/session-notes.md)
172871
- ` : "") + `
172872
- ${truncated}`;
172873
- const MIN_PRESERVED_TOKENS = 1e4;
172874
- const lastSummarized = getLastSummarizedCursorIndex();
172875
- let keepFromIndex = lastSummarized >= 0 ? lastSummarized + 1 : history.length;
172876
- let preservedTokens = estimateHistoryTokens(history.slice(keepFromIndex));
172877
- while (keepFromIndex > 0 && preservedTokens < MIN_PRESERVED_TOKENS) {
172878
- keepFromIndex--;
172879
- preservedTokens += estimateHistoryTokens([history[keepFromIndex]]);
173010
+ const contentParts = allModelParts.filter((part) => !part.thought);
173011
+ const consolidatedHistoryParts = [];
173012
+ for (const part of contentParts) {
173013
+ const lastPart = consolidatedHistoryParts[consolidatedHistoryParts.length - 1];
173014
+ if (lastPart?.text && isValidNonThoughtTextPart(lastPart) && isValidNonThoughtTextPart(part)) {
173015
+ lastPart.text += part.text;
173016
+ } else if (isValidContentPart(part)) {
173017
+ consolidatedHistoryParts.push(part);
173018
+ }
173019
+ }
173020
+ const contentText = consolidatedHistoryParts.filter((part) => part.text).map((part) => part.text).join("").trim();
173021
+ if (thoughtContentPart || contentText || hasToolCall2 || usageMetadata) {
173022
+ const contextWindowSize = this.config.getContentGeneratorConfig()?.contextWindowSize;
173023
+ this.chatRecordingService?.recordAssistantTurn({
173024
+ model,
173025
+ message: [
173026
+ ...thoughtContentPart ? [thoughtContentPart] : [],
173027
+ ...contentText ? [{ text: contentText }] : [],
173028
+ ...hasToolCall2 ? contentParts.filter((part) => part.functionCall).map((part) => ({ functionCall: part.functionCall })) : []
173029
+ ],
173030
+ tokens: usageMetadata,
173031
+ contextWindowSize
173032
+ });
173033
+ }
173034
+ if (!hasToolCall2 && (!hasFinishReason || !contentText)) {
173035
+ if (!hasFinishReason) {
173036
+ throw new InvalidStreamError("Model stream ended without a finish reason.", "NO_FINISH_REASON");
173037
+ } else {
173038
+ throw new InvalidStreamError("Model stream ended with empty response text.", "NO_RESPONSE_TEXT");
172880
173039
  }
172881
- keepFromIndex = adjustIndexForToolPairs(history, keepFromIndex);
172882
- const preservedTail = history.slice(keepFromIndex);
172883
- const summaryUserMsg = {
172884
- role: "user",
172885
- parts: [{ text: summaryText }]
172886
- };
172887
- const summaryAckMsg = {
172888
- role: "model",
172889
- parts: [
172890
- {
172891
- text: "Understood. I have the session summary and will continue from the current state."
172892
- }
172893
- ]
172894
- };
172895
- const newHistory = [summaryUserMsg, summaryAckMsg, ...preservedTail];
172896
- const newTokenCount = estimateHistoryTokens(newHistory);
172897
- if (newTokenCount >= originalTokenCount * 0.9)
172898
- return null;
172899
- return {
172900
- newHistory,
172901
- info: {
172902
- originalTokenCount,
172903
- newTokenCount,
172904
- compressionStatus: CompressionStatus.COMPRESSED
172905
- }
172906
- };
172907
- } catch {
172908
- return null;
172909
173040
  }
173041
+ this.history.push({
173042
+ role: "model",
173043
+ parts: [
173044
+ ...thoughtContentPart ? [thoughtContentPart] : [],
173045
+ ...consolidatedHistoryParts
173046
+ ]
173047
+ });
172910
173048
  }
172911
173049
  };
172912
- __name(estimateHistoryTokens, "estimateHistoryTokens");
172913
- __name(adjustIndexForToolPairs, "adjustIndexForToolPairs");
173050
+ __name(isSchemaDepthError, "isSchemaDepthError");
173051
+ __name(isInvalidArgumentError, "isInvalidArgumentError");
173052
+ }
173053
+ });
173054
+
173055
+ // packages/core/dist/src/backgroundShells/notifications.js
173056
+ function escapeXml(text) {
173057
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
173058
+ }
173059
+ function buildBackgroundTaskNotification(task) {
173060
+ const exitLine = task.exitCode !== void 0 && task.exitCode !== null ? `
173061
+ <exit_code>${task.exitCode}</exit_code>` : "";
173062
+ const summary = (() => {
173063
+ const desc2 = task.description || task.command;
173064
+ switch (task.status) {
173065
+ case "completed":
173066
+ return `Background command "${desc2}" completed${task.exitCode != null ? ` (exit code ${task.exitCode})` : ""}.`;
173067
+ case "failed":
173068
+ return `Background command "${desc2}" failed${task.exitCode != null ? ` with exit code ${task.exitCode}` : ""}.`;
173069
+ case "killed":
173070
+ return `Background command "${desc2}" was stopped.`;
173071
+ default:
173072
+ return `Background command "${desc2}" ended in unknown state.`;
173073
+ }
173074
+ })();
173075
+ return [
173076
+ "<task_notification>",
173077
+ `<task_id>${task.id}</task_id>`,
173078
+ `<output_file>${task.outputPath}</output_file>`,
173079
+ `<status>${task.status}</status>${exitLine}`,
173080
+ `<summary>${escapeXml(summary)}</summary>`,
173081
+ "</task_notification>",
173082
+ "",
173083
+ `Read ${task.outputPath} to see the full output.`
173084
+ ].join("\n");
173085
+ }
173086
+ var init_notifications = __esm({
173087
+ "packages/core/dist/src/backgroundShells/notifications.js"() {
173088
+ "use strict";
173089
+ init_esbuild_shims();
173090
+ __name(escapeXml, "escapeXml");
173091
+ __name(buildBackgroundTaskNotification, "buildBackgroundTaskNotification");
172914
173092
  }
172915
173093
  });
172916
173094
 
@@ -416607,7 +416785,7 @@ __name(getPackageJson, "getPackageJson");
416607
416785
  // packages/cli/src/utils/version.ts
416608
416786
  async function getCliVersion() {
416609
416787
  const pkgJson = await getPackageJson();
416610
- return "0.51.2";
416788
+ return "0.52.0";
416611
416789
  }
416612
416790
  __name(getCliVersion, "getCliVersion");
416613
416791
 
@@ -424807,7 +424985,7 @@ var formatDuration = /* @__PURE__ */ __name((milliseconds) => {
424807
424985
 
424808
424986
  // packages/cli/src/generated/git-commit.ts
424809
424987
  init_esbuild_shims();
424810
- var GIT_COMMIT_INFO = "a6b2897d3";
424988
+ var GIT_COMMIT_INFO = "0f21b967a";
424811
424989
 
424812
424990
  // packages/cli/src/utils/systemInfo.ts
424813
424991
  async function getNpmVersion() {
@@ -493609,7 +493787,7 @@ var QwenAgent = class {
493609
493787
  async initialize(args2) {
493610
493788
  this.clientCapabilities = args2.clientCapabilities;
493611
493789
  const authMethods = buildAuthMethods();
493612
- const version2 = "0.51.2";
493790
+ const version2 = "0.52.0";
493613
493791
  return {
493614
493792
  protocolVersion: PROTOCOL_VERSION,
493615
493793
  agentInfo: {
@@ -494696,11 +494874,8 @@ main().catch((error40) => {
494696
494874
  */
494697
494875
  /**
494698
494876
  * @license
494699
- * Copyright 2025 protoLabs Studio
494877
+ * Copyright 2026 protoCLI contributors
494700
494878
  * SPDX-License-Identifier: Apache-2.0
494701
- *
494702
- * Builds the <task_notification> blocks that get prepended to the next
494703
- * user query so the model sees backgrounded tasks finishing.
494704
494879
  */
494705
494880
  /**
494706
494881
  * @license
@@ -494756,6 +494931,14 @@ main().catch((error40) => {
494756
494931
  * where result payloads are large but the call names / return structure
494757
494932
  * still provide useful signal to the model.
494758
494933
  */
494934
+ /**
494935
+ * @license
494936
+ * Copyright 2025 protoLabs Studio
494937
+ * SPDX-License-Identifier: Apache-2.0
494938
+ *
494939
+ * Builds the <task_notification> blocks that get prepended to the next
494940
+ * user query so the model sees backgrounded tasks finishing.
494941
+ */
494759
494942
  /**
494760
494943
  * @license
494761
494944
  * Copyright 2025 protoLabs Studio
@@ -494824,11 +495007,6 @@ main().catch((error40) => {
494824
495007
  * By constructing the forked GeminiChat with identical generationConfig and
494825
495008
  * history prefix, the fork automatically benefits from prefix caching.
494826
495009
  */
494827
- /**
494828
- * @license
494829
- * Copyright 2026 protoCLI contributors
494830
- * SPDX-License-Identifier: Apache-2.0
494831
- */
494832
495010
  /**
494833
495011
  * @license
494834
495012
  * Copyright 2025 protoLabs Studio