dsh-ops 0.0.0-stage → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +202 -0
- package/LICENSE +30 -0
- package/NOTICE +106 -0
- package/PROVENANCE.md +435 -0
- package/README.en.md +126 -0
- package/README.md +115 -2
- package/README.zh.md +116 -0
- package/bin/dsh-ops.mjs +1216 -0
- package/cordis.patch.yml +160 -0
- package/docs/manual-validation.md +53 -0
- package/docs/release-0.2.1.md +72 -0
- package/docs/schema-baseline.json +64 -0
- package/docs/schema-current.json +84 -0
- package/docs/schema-measurement.md +17 -0
- package/dsh-plugin.json +88 -0
- package/icon.svg +12 -0
- package/lib/binary.js +409 -0
- package/lib/config.js +198 -0
- package/lib/handshake.js +252 -0
- package/lib/index.js +108 -0
- package/lib/jobs.js +42 -0
- package/lib/policy.js +64 -0
- package/lib/presentation.js +63 -0
- package/lib/profile-install.js +61 -0
- package/lib/rust.js +194 -0
- package/lib/session-shells.js +78 -0
- package/lib/shells.js +998 -0
- package/lib/tools.js +657 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +114 -4
- package/vendor/fastctx/Cargo.lock +3210 -0
- package/vendor/fastctx/Cargo.toml +94 -0
- package/vendor/fastctx/FORK.md +119 -0
- package/vendor/fastctx/LICENSE-APACHE +201 -0
- package/vendor/fastctx/NOTICE +40 -0
- package/vendor/fastctx/README.md +439 -0
- package/vendor/fastctx/THIRD_PARTY_LICENSES.md +17 -0
- package/vendor/fastctx/THIRD_PARTY_LICENSES_RUST.md +7914 -0
- package/vendor/fastctx/UPSTREAM.md +49 -0
- package/vendor/fastctx/build.rs +413 -0
- package/vendor/fastctx/src/background_status.rs +403 -0
- package/vendor/fastctx/src/binary.rs +75 -0
- package/vendor/fastctx/src/bounded_sort.rs +500 -0
- package/vendor/fastctx/src/budget.rs +781 -0
- package/vendor/fastctx/src/cli/mod.rs +110 -0
- package/vendor/fastctx/src/context_guard.rs +289 -0
- package/vendor/fastctx/src/control/mod.rs +6 -0
- package/vendor/fastctx/src/control/paths.rs +49 -0
- package/vendor/fastctx/src/control/settings.rs +753 -0
- package/vendor/fastctx/src/control/transaction.rs +531 -0
- package/vendor/fastctx/src/edit/document.rs +535 -0
- package/vendor/fastctx/src/edit/locks.rs +371 -0
- package/vendor/fastctx/src/edit/mod.rs +213 -0
- package/vendor/fastctx/src/edit/private_storage/unix.rs +315 -0
- package/vendor/fastctx/src/edit/private_storage/windows.rs +793 -0
- package/vendor/fastctx/src/edit/private_storage.rs +234 -0
- package/vendor/fastctx/src/edit/replace.rs +1030 -0
- package/vendor/fastctx/src/edit_server.rs +53 -0
- package/vendor/fastctx/src/encoding/reference_v011.rs +587 -0
- package/vendor/fastctx/src/encoding/snapshot_pipeline.rs +1678 -0
- package/vendor/fastctx/src/encoding.rs +1118 -0
- package/vendor/fastctx/src/file_executor.rs +1151 -0
- package/vendor/fastctx/src/file_snapshot.rs +1491 -0
- package/vendor/fastctx/src/glob_filter.rs +98 -0
- package/vendor/fastctx/src/glob_tool.rs +653 -0
- package/vendor/fastctx/src/grep_sink.rs +1162 -0
- package/vendor/fastctx/src/grep_tool.rs +2449 -0
- package/vendor/fastctx/src/lib.rs +45 -0
- package/vendor/fastctx/src/main.rs +15 -0
- package/vendor/fastctx/src/model.rs +51 -0
- package/vendor/fastctx/src/model_guidance.rs +62 -0
- package/vendor/fastctx/src/operation.rs +356 -0
- package/vendor/fastctx/src/ordered_window.rs +1235 -0
- package/vendor/fastctx/src/os_environment.rs +414 -0
- package/vendor/fastctx/src/path_codec.rs +850 -0
- package/vendor/fastctx/src/paths.rs +244 -0
- package/vendor/fastctx/src/process_identity.rs +763 -0
- package/vendor/fastctx/src/process_policy.rs +74 -0
- package/vendor/fastctx/src/read_tool/batch.rs +496 -0
- package/vendor/fastctx/src/read_tool/hex_file.rs +141 -0
- package/vendor/fastctx/src/read_tool/image_file.rs +88 -0
- package/vendor/fastctx/src/read_tool/mod.rs +245 -0
- package/vendor/fastctx/src/read_tool/pdf.rs +470 -0
- package/vendor/fastctx/src/read_tool/pdf_disabled.rs +47 -0
- package/vendor/fastctx/src/read_tool/pdf_engine.rs +664 -0
- package/vendor/fastctx/src/read_tool/text_file.rs +351 -0
- package/vendor/fastctx/src/render_plan.rs +468 -0
- package/vendor/fastctx/src/runtime/activity.rs +159 -0
- package/vendor/fastctx/src/runtime/hosts.rs +99 -0
- package/vendor/fastctx/src/runtime/journal.rs +556 -0
- package/vendor/fastctx/src/runtime/local_ipc.rs +186 -0
- package/vendor/fastctx/src/runtime/mod.rs +746 -0
- package/vendor/fastctx/src/runtime/protocol.rs +296 -0
- package/vendor/fastctx/src/runtime/session.rs +536 -0
- package/vendor/fastctx/src/runtime/windows_process.rs +66 -0
- package/vendor/fastctx/src/search_parallelism.rs +106 -0
- package/vendor/fastctx/src/search_text.rs +227 -0
- package/vendor/fastctx/src/server.rs +359 -0
- package/vendor/fastctx/src/server_manifest.rs +468 -0
- package/vendor/fastctx/src/server_support.rs +826 -0
- package/vendor/fastctx/src/session.rs +629 -0
- package/vendor/fastctx/src/shell/apply_patch_hint.rs +41 -0
- package/vendor/fastctx/src/shell/bash.rs +263 -0
- package/vendor/fastctx/src/shell/buffer.rs +108 -0
- package/vendor/fastctx/src/shell/encoding.rs +403 -0
- package/vendor/fastctx/src/shell/foreground.rs +115 -0
- package/vendor/fastctx/src/shell/jobs/admission.rs +91 -0
- package/vendor/fastctx/src/shell/jobs/background.rs +146 -0
- package/vendor/fastctx/src/shell/jobs/host.rs +830 -0
- package/vendor/fastctx/src/shell/jobs/identity.rs +29 -0
- package/vendor/fastctx/src/shell/jobs/mod.rs +1513 -0
- package/vendor/fastctx/src/shell/jobs/model.rs +244 -0
- package/vendor/fastctx/src/shell/jobs/output_log.rs +1148 -0
- package/vendor/fastctx/src/shell/jobs/store.rs +1300 -0
- package/vendor/fastctx/src/shell/mod.rs +345 -0
- package/vendor/fastctx/src/shell/normalize.rs +389 -0
- package/vendor/fastctx/src/shell/output.rs +406 -0
- package/vendor/fastctx/src/shell/process.rs +493 -0
- package/vendor/fastctx/src/shell_server.rs +156 -0
- package/vendor/fastctx/src/skip_report.rs +83 -0
- package/vendor/fastctx/src/stdio_transport.rs +177 -0
- package/vendor/fastctx/src/tool_schema.rs +204 -0
- package/vendor/fastctx/src/traversal.rs +846 -0
- package/vendor/fastctx/third-party/pdfium-7763/LICENSE +9 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/abseil.txt +202 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/agg23.txt +14 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/fast_float.txt +27 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/freetype.txt +169 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/icu.txt +542 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/lcms.txt +27 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libjpeg_turbo.ijg +260 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libjpeg_turbo.md +135 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libopenjpeg.txt +32 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libpng.txt +134 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libtiff.txt +21 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/llvm-libc.txt +278 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/pdfium.txt +230 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/simdutf.txt +18 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/zlib.txt +29 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
//! Strict incremental decoding, line collection, and budget closure for text reads.
|
|
2
|
+
|
|
3
|
+
use super::{
|
|
4
|
+
MAX_LINE_CHARS, TOTAL_COUNT_SIZE_LIMIT, UNBOUNDED_LINE_LIMIT, binary_error,
|
|
5
|
+
binary_error_message,
|
|
6
|
+
};
|
|
7
|
+
use crate::budget::{LineTokenCounter, TokenBudget, assemble_text, estimate_tokens};
|
|
8
|
+
use crate::encoding::{EncodingDecision, StreamDecodeFailure, validate_file_encoding};
|
|
9
|
+
use crate::model::ToolResponse;
|
|
10
|
+
use crate::paths::io_error_message;
|
|
11
|
+
use std::fs;
|
|
12
|
+
use std::path::Path;
|
|
13
|
+
|
|
14
|
+
pub(super) fn read_text_file(
|
|
15
|
+
path: &Path,
|
|
16
|
+
path_display: &str,
|
|
17
|
+
offset: Option<usize>,
|
|
18
|
+
limit: Option<usize>,
|
|
19
|
+
explicit_encoding: Option<&str>,
|
|
20
|
+
binary_type: Option<&str>,
|
|
21
|
+
budget: TokenBudget,
|
|
22
|
+
) -> ToolResponse {
|
|
23
|
+
let offset = offset.unwrap_or(1);
|
|
24
|
+
let limit = limit.unwrap_or(UNBOUNDED_LINE_LIMIT);
|
|
25
|
+
if offset == 0 {
|
|
26
|
+
return ToolResponse::error("Invalid offset value: 0. Expected an integer >= 1.");
|
|
27
|
+
}
|
|
28
|
+
if limit == 0 {
|
|
29
|
+
return ToolResponse::error("Invalid limit value: 0. Expected an integer >= 1.");
|
|
30
|
+
}
|
|
31
|
+
let file_size = match fs::metadata(path) {
|
|
32
|
+
Ok(metadata) => metadata.len(),
|
|
33
|
+
Err(error) => return ToolResponse::error(io_error_message(path, &error)),
|
|
34
|
+
};
|
|
35
|
+
let validated = match validate_file_encoding(path, explicit_encoding) {
|
|
36
|
+
Ok(EncodingDecision::Text(validated)) => validated,
|
|
37
|
+
Ok(EncodingDecision::Binary) => return binary_error(path_display, binary_type),
|
|
38
|
+
Ok(EncodingDecision::Rejected(rejection)) => {
|
|
39
|
+
return ToolResponse::error(rejection.message(path_display));
|
|
40
|
+
}
|
|
41
|
+
Err(error) => return ToolResponse::error(io_error_message(path, &error)),
|
|
42
|
+
};
|
|
43
|
+
if validated.total_lines == 0 {
|
|
44
|
+
return ToolResponse::text("Warning: the file exists but is empty.");
|
|
45
|
+
}
|
|
46
|
+
let transcoding_note = validated.transcoding_note();
|
|
47
|
+
|
|
48
|
+
let total_is_known = file_size <= TOTAL_COUNT_SIZE_LIMIT;
|
|
49
|
+
let mut collector = LineCollector::new(offset, limit, budget.value, total_is_known);
|
|
50
|
+
let exhausted = match validated.stream_text(path, |chunk| collector.push(chunk)) {
|
|
51
|
+
Ok(exhausted) => exhausted,
|
|
52
|
+
Err(StreamDecodeFailure::Io(error)) => {
|
|
53
|
+
return ToolResponse::error(io_error_message(path, &error));
|
|
54
|
+
}
|
|
55
|
+
Err(StreamDecodeFailure::Malformed) => {
|
|
56
|
+
return ToolResponse::error(validated.malformed_rejection().message(path_display));
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
if exhausted {
|
|
60
|
+
collector.finish_eof();
|
|
61
|
+
}
|
|
62
|
+
collector.into_response(transcoding_note, budget)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
pub(super) struct BatchTextContent {
|
|
66
|
+
pub(super) first: usize,
|
|
67
|
+
pub(super) lines: Vec<String>,
|
|
68
|
+
pub(super) total_lines: usize,
|
|
69
|
+
pub(super) total_is_known: bool,
|
|
70
|
+
pub(super) transcoding_note: Option<String>,
|
|
71
|
+
pub(super) slice_complete: bool,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#[allow(clippy::too_many_arguments)]
|
|
75
|
+
pub(super) fn read_batch_text_file(
|
|
76
|
+
path: &Path,
|
|
77
|
+
path_display: &str,
|
|
78
|
+
offset: Option<usize>,
|
|
79
|
+
limit: Option<usize>,
|
|
80
|
+
explicit_encoding: Option<&str>,
|
|
81
|
+
binary_type: Option<&str>,
|
|
82
|
+
collection_budget: usize,
|
|
83
|
+
) -> Result<BatchTextContent, String> {
|
|
84
|
+
let offset = offset.unwrap_or(1);
|
|
85
|
+
let limit = limit.unwrap_or(UNBOUNDED_LINE_LIMIT);
|
|
86
|
+
let file_size = fs::metadata(path)
|
|
87
|
+
.map_err(|error| io_error_message(path, &error))?
|
|
88
|
+
.len();
|
|
89
|
+
let validated = match validate_file_encoding(path, explicit_encoding) {
|
|
90
|
+
Ok(EncodingDecision::Text(validated)) => validated,
|
|
91
|
+
Ok(EncodingDecision::Binary) => {
|
|
92
|
+
return Err(binary_error_message(path_display, binary_type));
|
|
93
|
+
}
|
|
94
|
+
Ok(EncodingDecision::Rejected(rejection)) => {
|
|
95
|
+
return Err(rejection.message(path_display));
|
|
96
|
+
}
|
|
97
|
+
Err(error) => return Err(io_error_message(path, &error)),
|
|
98
|
+
};
|
|
99
|
+
if validated.total_lines == 0 {
|
|
100
|
+
return Err("Warning: the file exists but is empty.".to_string());
|
|
101
|
+
}
|
|
102
|
+
if validated.total_lines < offset {
|
|
103
|
+
let noun = if validated.total_lines == 1 {
|
|
104
|
+
"line"
|
|
105
|
+
} else {
|
|
106
|
+
"lines"
|
|
107
|
+
};
|
|
108
|
+
return Err(format!(
|
|
109
|
+
"Warning: the file has only {} {noun}, but offset={offset} was requested.",
|
|
110
|
+
validated.total_lines
|
|
111
|
+
));
|
|
112
|
+
}
|
|
113
|
+
let total_lines = validated.total_lines;
|
|
114
|
+
let total_is_known = file_size <= TOTAL_COUNT_SIZE_LIMIT;
|
|
115
|
+
let transcoding_note = validated.transcoding_note();
|
|
116
|
+
let mut collector = LineCollector::new(offset, limit, collection_budget, total_is_known);
|
|
117
|
+
let exhausted = match validated.stream_text(path, |chunk| collector.push(chunk)) {
|
|
118
|
+
Ok(exhausted) => exhausted,
|
|
119
|
+
Err(StreamDecodeFailure::Io(error)) => return Err(io_error_message(path, &error)),
|
|
120
|
+
Err(StreamDecodeFailure::Malformed) => {
|
|
121
|
+
return Err(validated.malformed_rejection().message(path_display));
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
if exhausted {
|
|
125
|
+
collector.finish_eof();
|
|
126
|
+
}
|
|
127
|
+
Ok(BatchTextContent {
|
|
128
|
+
first: offset,
|
|
129
|
+
lines: collector.rendered,
|
|
130
|
+
total_lines,
|
|
131
|
+
total_is_known,
|
|
132
|
+
transcoding_note,
|
|
133
|
+
slice_complete: !collector.storage_saturated,
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
struct LineCollector {
|
|
138
|
+
offset: usize,
|
|
139
|
+
request_end: usize,
|
|
140
|
+
budget: usize,
|
|
141
|
+
total_is_known: bool,
|
|
142
|
+
line_number: usize,
|
|
143
|
+
current_prefix: String,
|
|
144
|
+
current_chars: usize,
|
|
145
|
+
current_ends_with_cr: bool,
|
|
146
|
+
decoded_any: bool,
|
|
147
|
+
last_was_newline: bool,
|
|
148
|
+
rendered: Vec<String>,
|
|
149
|
+
body_tokens: LineTokenCounter,
|
|
150
|
+
storage_saturated: bool,
|
|
151
|
+
total_lines: usize,
|
|
152
|
+
stopped_early: bool,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
impl LineCollector {
|
|
156
|
+
fn new(offset: usize, limit: usize, budget: usize, total_is_known: bool) -> Self {
|
|
157
|
+
Self {
|
|
158
|
+
offset,
|
|
159
|
+
request_end: offset.saturating_add(limit.saturating_sub(1)),
|
|
160
|
+
budget,
|
|
161
|
+
total_is_known,
|
|
162
|
+
line_number: 1,
|
|
163
|
+
current_prefix: String::new(),
|
|
164
|
+
current_chars: 0,
|
|
165
|
+
current_ends_with_cr: false,
|
|
166
|
+
decoded_any: false,
|
|
167
|
+
last_was_newline: false,
|
|
168
|
+
rendered: Vec::new(),
|
|
169
|
+
body_tokens: LineTokenCounter::default(),
|
|
170
|
+
storage_saturated: false,
|
|
171
|
+
total_lines: 0,
|
|
172
|
+
stopped_early: false,
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
fn push(&mut self, text: &str) -> bool {
|
|
177
|
+
for ch in text.chars() {
|
|
178
|
+
self.decoded_any = true;
|
|
179
|
+
if self.line_number > self.request_end && !self.total_is_known {
|
|
180
|
+
self.total_lines = self.line_number;
|
|
181
|
+
self.stopped_early = true;
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
if ch == '\n' {
|
|
185
|
+
self.last_was_newline = true;
|
|
186
|
+
if !self.finish_line() {
|
|
187
|
+
self.stopped_early = true;
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
self.last_was_newline = false;
|
|
193
|
+
self.current_chars = self.current_chars.saturating_add(1);
|
|
194
|
+
self.current_ends_with_cr = ch == '\r';
|
|
195
|
+
if self.should_capture_current() && self.current_chars <= MAX_LINE_CHARS {
|
|
196
|
+
self.current_prefix.push(ch);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
true
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
fn should_capture_current(&self) -> bool {
|
|
203
|
+
!self.storage_saturated
|
|
204
|
+
&& self.line_number >= self.offset
|
|
205
|
+
&& self.line_number <= self.request_end
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
fn finish_line(&mut self) -> bool {
|
|
209
|
+
self.total_lines = self.line_number;
|
|
210
|
+
if self.line_number > self.request_end {
|
|
211
|
+
self.reset_line();
|
|
212
|
+
return self.total_is_known;
|
|
213
|
+
}
|
|
214
|
+
if self.line_number >= self.offset && !self.storage_saturated {
|
|
215
|
+
if self.current_ends_with_cr && self.current_chars <= MAX_LINE_CHARS {
|
|
216
|
+
self.current_prefix.pop();
|
|
217
|
+
}
|
|
218
|
+
let total_chars = self
|
|
219
|
+
.current_chars
|
|
220
|
+
.saturating_sub(usize::from(self.current_ends_with_cr));
|
|
221
|
+
let content = if total_chars <= MAX_LINE_CHARS {
|
|
222
|
+
std::mem::take(&mut self.current_prefix)
|
|
223
|
+
} else {
|
|
224
|
+
format!(
|
|
225
|
+
"{}... [line truncated: {total_chars} chars total]",
|
|
226
|
+
self.current_prefix
|
|
227
|
+
)
|
|
228
|
+
};
|
|
229
|
+
let rendered = format!("{}\t{content}", self.line_number);
|
|
230
|
+
let body_tokens = self.body_tokens.push(&rendered);
|
|
231
|
+
self.rendered.push(rendered);
|
|
232
|
+
if body_tokens > self.budget {
|
|
233
|
+
self.storage_saturated = true;
|
|
234
|
+
if !self.total_is_known {
|
|
235
|
+
self.reset_line();
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
self.reset_line();
|
|
241
|
+
true
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
fn reset_line(&mut self) {
|
|
245
|
+
self.line_number = self.line_number.saturating_add(1);
|
|
246
|
+
self.current_prefix.clear();
|
|
247
|
+
self.current_chars = 0;
|
|
248
|
+
self.current_ends_with_cr = false;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
fn finish_eof(&mut self) {
|
|
252
|
+
if self.last_was_newline || self.decoded_any || self.current_chars > 0 {
|
|
253
|
+
let _ = self.finish_line();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
fn into_response(
|
|
258
|
+
mut self,
|
|
259
|
+
transcoding_note: Option<String>,
|
|
260
|
+
budget: TokenBudget,
|
|
261
|
+
) -> ToolResponse {
|
|
262
|
+
if self.total_lines < self.offset {
|
|
263
|
+
let noun = if self.total_lines == 1 {
|
|
264
|
+
"line"
|
|
265
|
+
} else {
|
|
266
|
+
"lines"
|
|
267
|
+
};
|
|
268
|
+
return ToolResponse::text(format!(
|
|
269
|
+
"Warning: the file has only {} {noun}, but offset={} was requested.",
|
|
270
|
+
self.total_lines, self.offset,
|
|
271
|
+
));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
loop {
|
|
275
|
+
let shown = self.rendered.len();
|
|
276
|
+
if shown == 0 && (self.stopped_early || self.storage_saturated) {
|
|
277
|
+
return ToolResponse::error(format!(
|
|
278
|
+
"{}={} is too small to return the required continuation note. Increase it and retry.",
|
|
279
|
+
budget.variable, budget.value
|
|
280
|
+
));
|
|
281
|
+
}
|
|
282
|
+
let last = self.offset.saturating_add(shown.saturating_sub(1));
|
|
283
|
+
let truncated = self.stopped_early
|
|
284
|
+
|| self.storage_saturated
|
|
285
|
+
|| (shown > 0 && last < self.total_lines);
|
|
286
|
+
let notes = read_notes(
|
|
287
|
+
transcoding_note.as_deref(),
|
|
288
|
+
truncated,
|
|
289
|
+
self.offset,
|
|
290
|
+
shown,
|
|
291
|
+
self.total_lines,
|
|
292
|
+
self.total_is_known,
|
|
293
|
+
);
|
|
294
|
+
let output = assemble_text(&self.rendered, ¬es);
|
|
295
|
+
if estimate_tokens(&output) <= budget.value {
|
|
296
|
+
return ToolResponse::text(output);
|
|
297
|
+
}
|
|
298
|
+
if self.rendered.pop().is_none() {
|
|
299
|
+
return ToolResponse::error(format!(
|
|
300
|
+
"{}={} is too small to return the required continuation note. Increase it and retry.",
|
|
301
|
+
budget.variable, budget.value
|
|
302
|
+
));
|
|
303
|
+
}
|
|
304
|
+
self.storage_saturated = true;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
fn read_notes(
|
|
310
|
+
transcoding_note: Option<&str>,
|
|
311
|
+
truncated: bool,
|
|
312
|
+
offset: usize,
|
|
313
|
+
shown: usize,
|
|
314
|
+
total: usize,
|
|
315
|
+
total_is_known: bool,
|
|
316
|
+
) -> Vec<String> {
|
|
317
|
+
let mut notes = Vec::new();
|
|
318
|
+
if let Some(note) = transcoding_note {
|
|
319
|
+
notes.push(note.to_string());
|
|
320
|
+
}
|
|
321
|
+
if shown > 0 {
|
|
322
|
+
let last = offset + shown - 1;
|
|
323
|
+
let span = line_span(offset, last);
|
|
324
|
+
if truncated {
|
|
325
|
+
if total_is_known {
|
|
326
|
+
notes.push(format!(
|
|
327
|
+
"(Partial: {span} of {total} shown. Continue with offset={}.)",
|
|
328
|
+
last + 1
|
|
329
|
+
));
|
|
330
|
+
} else {
|
|
331
|
+
notes.push(format!(
|
|
332
|
+
"(Partial: {span} shown. Continue with offset={}.)",
|
|
333
|
+
last + 1
|
|
334
|
+
));
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
notes.push(format!(
|
|
338
|
+
"(Complete: reached end of file; {span} of {total} shown.)"
|
|
339
|
+
));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
notes
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
fn line_span(first: usize, last: usize) -> String {
|
|
346
|
+
if first == last {
|
|
347
|
+
format!("line {first}")
|
|
348
|
+
} else {
|
|
349
|
+
format!("lines {first}-{last}")
|
|
350
|
+
}
|
|
351
|
+
}
|