@juspay/neurolink 12.29.2 → 12.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -2
- package/dist/browser/neurolink.min.js +357 -355
- package/dist/cli/factories/commandFactory.js +2 -1
- package/dist/cli/proxy-clients/codex.js +112 -42
- package/dist/cli/proxy-clients/grok.d.ts +15 -1
- package/dist/cli/proxy-clients/grok.js +186 -28
- package/dist/cli/proxy-clients/snapshot.d.ts +12 -0
- package/dist/cli/proxy-clients/snapshot.js +37 -4
- package/dist/cli/proxy-clients/tomlSections.d.ts +74 -0
- package/dist/cli/proxy-clients/tomlSections.js +365 -0
- package/dist/core/baseProvider.js +1 -0
- package/dist/core/modules/MessageBuilder.js +8 -0
- package/dist/processors/media/VideoProcessor.d.ts +56 -4
- package/dist/processors/media/VideoProcessor.js +263 -19
- package/dist/types/file.d.ts +20 -2
- package/dist/types/generate.d.ts +20 -26
- package/dist/types/processor.d.ts +43 -0
- package/dist/types/proxyClient.d.ts +28 -3
- package/dist/types/stream.d.ts +11 -11
- package/dist/utils/fileDetector.d.ts +15 -0
- package/dist/utils/fileDetector.js +40 -7
- package/dist/utils/mediaDuration.d.ts +19 -0
- package/dist/utils/mediaDuration.js +23 -0
- package/dist/utils/messageBuilder.js +4 -15
- package/dist/utils/multimodalOptionsBuilder.d.ts +2 -0
- package/dist/utils/multimodalOptionsBuilder.js +7 -0
- package/docs-site/static/search-index.json +2 -2
- package/package.json +3 -2
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Line-level TOML reading for the proxy's client-config writers.
|
|
3
|
+
*
|
|
4
|
+
* Grok and Codex keep their settings in TOML files the user and the CLI
|
|
5
|
+
* itself also edit, and the writers must find what the proxy wrote there
|
|
6
|
+
* without trusting its comment markers: Grok's own save drops every comment.
|
|
7
|
+
* No TOML parser is a dependency, and a parse-and-serialize round trip would
|
|
8
|
+
* reformat the user's file anyway, so this reads just enough structure to
|
|
9
|
+
* edit text in place: table headers, strings, keys and assignments. Every
|
|
10
|
+
* line keeps its own terminator, so text a writer does not own round-trips
|
|
11
|
+
* byte for byte.
|
|
12
|
+
*/
|
|
13
|
+
import type { CliTomlSection } from "../../types/index.js";
|
|
14
|
+
export declare function splitTomlSections(text: string): CliTomlSection[];
|
|
15
|
+
/**
|
|
16
|
+
* A line of TOML with every string replaced by `@<n>@` and any comment
|
|
17
|
+
* dropped, so text inside a string can never read as a key or an assignment.
|
|
18
|
+
* `strings[n]` is the decoded value. `@` cannot occur outside a string in
|
|
19
|
+
* valid TOML.
|
|
20
|
+
*/
|
|
21
|
+
export declare function maskTomlStrings(line: string): {
|
|
22
|
+
masked: string;
|
|
23
|
+
strings: string[];
|
|
24
|
+
};
|
|
25
|
+
export declare function parseMaskedAssignment(masked: string, strings: readonly string[]): {
|
|
26
|
+
path: string[];
|
|
27
|
+
value: string;
|
|
28
|
+
} | null;
|
|
29
|
+
/**
|
|
30
|
+
* The string a masked value starts with. Only the start counts, so a value
|
|
31
|
+
* with damage after it (a literal `\n` left by a bad paste) is still read.
|
|
32
|
+
*/
|
|
33
|
+
export declare function maskedStringValue(value: string, strings: readonly string[]): string | undefined;
|
|
34
|
+
/** The pairs directly inside a one-line inline table. */
|
|
35
|
+
export declare function inlineTablePairs(value: string, strings: readonly string[]): {
|
|
36
|
+
path: string[];
|
|
37
|
+
value: string;
|
|
38
|
+
}[];
|
|
39
|
+
/** The dotted key a statement assigns, and its value when that is a string. */
|
|
40
|
+
export declare function readTomlAssignment(line: string): {
|
|
41
|
+
path: string[];
|
|
42
|
+
value: string | undefined;
|
|
43
|
+
} | null;
|
|
44
|
+
/**
|
|
45
|
+
* The lines of a section that are TOML rather than the inside of a
|
|
46
|
+
* multi-line string; `statement` marks those that start a key/value pair
|
|
47
|
+
* rather than continue an array or inline table.
|
|
48
|
+
*/
|
|
49
|
+
export declare function tomlCodeLines(section: CliTomlSection): {
|
|
50
|
+
line: string;
|
|
51
|
+
statement: boolean;
|
|
52
|
+
}[];
|
|
53
|
+
export declare function readStringValue(section: CliTomlSection, key: string): string | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* Names of the `<parent>.<name>` tables defined by dotted keys instead of a
|
|
56
|
+
* `[<parent>.<name>]` header: `claude-opus-4-6.base_url = ...` under
|
|
57
|
+
* `[model]`, or `model.claude-opus-4-6.base_url = ...` before the first
|
|
58
|
+
* header.
|
|
59
|
+
*/
|
|
60
|
+
export declare function dottedTableIds(sections: readonly CliTomlSection[], parent: string): string[];
|
|
61
|
+
/**
|
|
62
|
+
* How many of an owned section's lines are the writer's. Comments after its
|
|
63
|
+
* last key belong to what follows, so a note the user wrote above their own
|
|
64
|
+
* next table survives. `managedLines` are the writer's own comment lines,
|
|
65
|
+
* which are dropped wherever they sit.
|
|
66
|
+
*/
|
|
67
|
+
export declare function ownedLineCount(section: CliTomlSection, managedLines: ReadonlySet<string>): number;
|
|
68
|
+
/**
|
|
69
|
+
* A table declared twice is a TOML error, and neither Grok nor Codex will
|
|
70
|
+
* start on one.
|
|
71
|
+
* Each `[[array]]` element opens a fresh scope for everything under it, so
|
|
72
|
+
* `[mcp_servers.env]` under two elements is not a repeat.
|
|
73
|
+
*/
|
|
74
|
+
export declare function findDuplicateTable(text: string): string | undefined;
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Line-level TOML reading for the proxy's client-config writers.
|
|
3
|
+
*
|
|
4
|
+
* Grok and Codex keep their settings in TOML files the user and the CLI
|
|
5
|
+
* itself also edit, and the writers must find what the proxy wrote there
|
|
6
|
+
* without trusting its comment markers: Grok's own save drops every comment.
|
|
7
|
+
* No TOML parser is a dependency, and a parse-and-serialize round trip would
|
|
8
|
+
* reformat the user's file anyway, so this reads just enough structure to
|
|
9
|
+
* edit text in place: table headers, strings, keys and assignments. Every
|
|
10
|
+
* line keeps its own terminator, so text a writer does not own round-trips
|
|
11
|
+
* byte for byte.
|
|
12
|
+
*/
|
|
13
|
+
function splitLinesKeepingEnds(text) {
|
|
14
|
+
return text.match(/[^\n]*\n|[^\n]+$/g) ?? [];
|
|
15
|
+
}
|
|
16
|
+
function readTomlKey(text) {
|
|
17
|
+
const bare = text.match(/^[A-Za-z0-9_-]+/);
|
|
18
|
+
if (bare) {
|
|
19
|
+
return { value: bare[0], length: bare[0].length };
|
|
20
|
+
}
|
|
21
|
+
const basic = text.match(/^"(?:[^"\\]|\\.)*"/);
|
|
22
|
+
if (basic) {
|
|
23
|
+
let value;
|
|
24
|
+
try {
|
|
25
|
+
value = JSON.parse(basic[0]);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
value = undefined;
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
value: typeof value === "string" ? value : basic[0].slice(1, -1),
|
|
32
|
+
length: basic[0].length,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const literal = text.match(/^'[^']*'/);
|
|
36
|
+
if (literal) {
|
|
37
|
+
return { value: literal[0].slice(1, -1), length: literal[0].length };
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
function parseTomlTableHeader(line) {
|
|
42
|
+
const text = line.trim();
|
|
43
|
+
if (!text.startsWith("[")) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const arrayTable = text.startsWith("[[");
|
|
47
|
+
const path = [];
|
|
48
|
+
let rest = text.slice(arrayTable ? 2 : 1);
|
|
49
|
+
for (;;) {
|
|
50
|
+
rest = rest.trimStart();
|
|
51
|
+
const key = readTomlKey(rest);
|
|
52
|
+
if (key === null) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
path.push(key.value);
|
|
56
|
+
rest = rest.slice(key.length).trimStart();
|
|
57
|
+
if (!rest.startsWith(".")) {
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
rest = rest.slice(1);
|
|
61
|
+
}
|
|
62
|
+
const close = arrayTable ? "]]" : "]";
|
|
63
|
+
if (!rest.startsWith(close)) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const tail = rest.slice(close.length).trim();
|
|
67
|
+
return tail.length === 0 || tail.startsWith("#")
|
|
68
|
+
? { path, arrayTable }
|
|
69
|
+
: null;
|
|
70
|
+
}
|
|
71
|
+
function advanceTomlScan(line, state) {
|
|
72
|
+
let { multiline, depth } = state;
|
|
73
|
+
let i = 0;
|
|
74
|
+
while (i < line.length) {
|
|
75
|
+
if (multiline !== null) {
|
|
76
|
+
if (multiline === '"""' && line[i] === "\\") {
|
|
77
|
+
i += 2;
|
|
78
|
+
}
|
|
79
|
+
else if (line.startsWith(multiline, i)) {
|
|
80
|
+
// Up to two quotes of content may sit against the closing delimiter.
|
|
81
|
+
const quote = multiline[0];
|
|
82
|
+
i += 3;
|
|
83
|
+
while (line[i] === quote) {
|
|
84
|
+
i += 1;
|
|
85
|
+
}
|
|
86
|
+
multiline = null;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
i += 1;
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const ch = line[i];
|
|
94
|
+
if (ch === "#") {
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
if (line.startsWith('"""', i) || line.startsWith("'''", i)) {
|
|
98
|
+
multiline = line.startsWith('"""', i) ? '"""' : "'''";
|
|
99
|
+
i += 3;
|
|
100
|
+
}
|
|
101
|
+
else if (ch === '"') {
|
|
102
|
+
i += 1;
|
|
103
|
+
while (i < line.length && line[i] !== '"') {
|
|
104
|
+
i += line[i] === "\\" ? 2 : 1;
|
|
105
|
+
}
|
|
106
|
+
i += 1;
|
|
107
|
+
}
|
|
108
|
+
else if (ch === "'") {
|
|
109
|
+
const end = line.indexOf("'", i + 1);
|
|
110
|
+
i = end === -1 ? line.length : end + 1;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
if (ch === "[" || ch === "{") {
|
|
114
|
+
depth += 1;
|
|
115
|
+
}
|
|
116
|
+
else if (ch === "]" || ch === "}") {
|
|
117
|
+
depth = Math.max(0, depth - 1);
|
|
118
|
+
}
|
|
119
|
+
i += 1;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return { multiline, depth };
|
|
123
|
+
}
|
|
124
|
+
export function splitTomlSections(text) {
|
|
125
|
+
const sections = [];
|
|
126
|
+
let current = { path: null, arrayTable: false, lines: [] };
|
|
127
|
+
let state = { multiline: null, depth: 0 };
|
|
128
|
+
for (const line of splitLinesKeepingEnds(text)) {
|
|
129
|
+
const header = state.multiline === null && state.depth === 0
|
|
130
|
+
? parseTomlTableHeader(line)
|
|
131
|
+
: null;
|
|
132
|
+
if (header) {
|
|
133
|
+
sections.push(current);
|
|
134
|
+
current = { ...header, lines: [line] };
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
current.lines.push(line);
|
|
138
|
+
state = advanceTomlScan(line, state);
|
|
139
|
+
}
|
|
140
|
+
sections.push(current);
|
|
141
|
+
return sections;
|
|
142
|
+
}
|
|
143
|
+
function decodeBasicString(inner) {
|
|
144
|
+
try {
|
|
145
|
+
const value = JSON.parse(`"${inner}"`);
|
|
146
|
+
return typeof value === "string" ? value : inner;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return inner;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* A line of TOML with every string replaced by `@<n>@` and any comment
|
|
154
|
+
* dropped, so text inside a string can never read as a key or an assignment.
|
|
155
|
+
* `strings[n]` is the decoded value. `@` cannot occur outside a string in
|
|
156
|
+
* valid TOML.
|
|
157
|
+
*/
|
|
158
|
+
export function maskTomlStrings(line) {
|
|
159
|
+
const strings = [];
|
|
160
|
+
let masked = "";
|
|
161
|
+
let i = 0;
|
|
162
|
+
while (i < line.length) {
|
|
163
|
+
const ch = line[i];
|
|
164
|
+
if (ch === "#") {
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
const triple = line.startsWith('"""', i)
|
|
168
|
+
? '"""'
|
|
169
|
+
: line.startsWith("'''", i)
|
|
170
|
+
? "'''"
|
|
171
|
+
: null;
|
|
172
|
+
if (triple !== null || ch === '"' || ch === "'") {
|
|
173
|
+
const delimiter = triple ?? ch;
|
|
174
|
+
let end = i + delimiter.length;
|
|
175
|
+
while (end < line.length && !line.startsWith(delimiter, end)) {
|
|
176
|
+
end += delimiter.startsWith('"') && line[end] === "\\" ? 2 : 1;
|
|
177
|
+
}
|
|
178
|
+
// Up to two quotes of content may sit against a triple closer.
|
|
179
|
+
for (let extra = 0; triple !== null &&
|
|
180
|
+
extra < 2 &&
|
|
181
|
+
line[end + delimiter.length] === delimiter[0]; extra += 1) {
|
|
182
|
+
end += 1;
|
|
183
|
+
}
|
|
184
|
+
const inner = line.slice(i + delimiter.length, Math.min(end, line.length));
|
|
185
|
+
strings.push(delimiter.startsWith("'") ? inner : decodeBasicString(inner));
|
|
186
|
+
masked += `@${strings.length - 1}@`;
|
|
187
|
+
i = end + delimiter.length;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
masked += ch;
|
|
191
|
+
i += 1;
|
|
192
|
+
}
|
|
193
|
+
return { masked, strings };
|
|
194
|
+
}
|
|
195
|
+
const TOML_KEY_PART = String.raw `(?:[A-Za-z0-9_-]+|@\d+@)`;
|
|
196
|
+
const TOML_ASSIGNMENT = new RegExp(String.raw `^\s*(${TOML_KEY_PART}(?:\s*\.\s*${TOML_KEY_PART})*)\s*=\s*(.*?)\s*$`);
|
|
197
|
+
function unmaskKeyPart(part, strings) {
|
|
198
|
+
const token = part.match(/^@(\d+)@$/);
|
|
199
|
+
return token ? strings[Number(token[1])] : part;
|
|
200
|
+
}
|
|
201
|
+
export function parseMaskedAssignment(masked, strings) {
|
|
202
|
+
const match = masked.match(TOML_ASSIGNMENT);
|
|
203
|
+
return match
|
|
204
|
+
? {
|
|
205
|
+
path: match[1]
|
|
206
|
+
.split(".")
|
|
207
|
+
.map((part) => unmaskKeyPart(part.trim(), strings)),
|
|
208
|
+
value: match[2],
|
|
209
|
+
}
|
|
210
|
+
: null;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* The string a masked value starts with. Only the start counts, so a value
|
|
214
|
+
* with damage after it (a literal `\n` left by a bad paste) is still read.
|
|
215
|
+
*/
|
|
216
|
+
export function maskedStringValue(value, strings) {
|
|
217
|
+
const token = value.match(/^@(\d+)@/);
|
|
218
|
+
return token ? strings[Number(token[1])] : undefined;
|
|
219
|
+
}
|
|
220
|
+
/** The pairs directly inside a one-line inline table. */
|
|
221
|
+
export function inlineTablePairs(value, strings) {
|
|
222
|
+
if (!value.startsWith("{") || !value.endsWith("}")) {
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
const parts = [];
|
|
226
|
+
let depth = 0;
|
|
227
|
+
let start = 1;
|
|
228
|
+
for (let i = 1; i < value.length - 1; i += 1) {
|
|
229
|
+
if (value[i] === "{" || value[i] === "[") {
|
|
230
|
+
depth += 1;
|
|
231
|
+
}
|
|
232
|
+
else if (value[i] === "}" || value[i] === "]") {
|
|
233
|
+
depth -= 1;
|
|
234
|
+
}
|
|
235
|
+
else if (value[i] === "," && depth === 0) {
|
|
236
|
+
parts.push(value.slice(start, i));
|
|
237
|
+
start = i + 1;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
parts.push(value.slice(start, value.length - 1));
|
|
241
|
+
return parts.flatMap((part) => {
|
|
242
|
+
const pair = parseMaskedAssignment(part, strings);
|
|
243
|
+
return pair ? [pair] : [];
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
/** The dotted key a statement assigns, and its value when that is a string. */
|
|
247
|
+
export function readTomlAssignment(line) {
|
|
248
|
+
const { masked, strings } = maskTomlStrings(line);
|
|
249
|
+
const assignment = parseMaskedAssignment(masked, strings);
|
|
250
|
+
return assignment
|
|
251
|
+
? {
|
|
252
|
+
path: assignment.path,
|
|
253
|
+
value: maskedStringValue(assignment.value, strings),
|
|
254
|
+
}
|
|
255
|
+
: null;
|
|
256
|
+
}
|
|
257
|
+
function isTomlContentLine(line) {
|
|
258
|
+
const text = line.trim();
|
|
259
|
+
return text.length > 0 && !text.startsWith("#");
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* The lines of a section that are TOML rather than the inside of a
|
|
263
|
+
* multi-line string; `statement` marks those that start a key/value pair
|
|
264
|
+
* rather than continue an array or inline table.
|
|
265
|
+
*/
|
|
266
|
+
export function tomlCodeLines(section) {
|
|
267
|
+
const code = [];
|
|
268
|
+
let state = { multiline: null, depth: 0 };
|
|
269
|
+
section.lines.forEach((line, index) => {
|
|
270
|
+
const isHeader = index === 0 && section.path !== null;
|
|
271
|
+
if (!isHeader && state.multiline === null) {
|
|
272
|
+
code.push({ line, statement: state.depth === 0 });
|
|
273
|
+
}
|
|
274
|
+
if (!isHeader) {
|
|
275
|
+
state = advanceTomlScan(line, state);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
return code;
|
|
279
|
+
}
|
|
280
|
+
export function readStringValue(section, key) {
|
|
281
|
+
for (const { line, statement } of tomlCodeLines(section)) {
|
|
282
|
+
const assignment = statement ? readTomlAssignment(line) : null;
|
|
283
|
+
if (assignment?.path.length === 1 && assignment.path[0] === key) {
|
|
284
|
+
return assignment.value;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return undefined;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Names of the `<parent>.<name>` tables defined by dotted keys instead of a
|
|
291
|
+
* `[<parent>.<name>]` header: `claude-opus-4-6.base_url = ...` under
|
|
292
|
+
* `[model]`, or `model.claude-opus-4-6.base_url = ...` before the first
|
|
293
|
+
* header.
|
|
294
|
+
*/
|
|
295
|
+
export function dottedTableIds(sections, parent) {
|
|
296
|
+
return sections.flatMap((section) => {
|
|
297
|
+
const underParent = !section.arrayTable &&
|
|
298
|
+
section.path?.length === 1 &&
|
|
299
|
+
section.path[0] === parent;
|
|
300
|
+
if (!underParent && section.path !== null) {
|
|
301
|
+
return [];
|
|
302
|
+
}
|
|
303
|
+
return tomlCodeLines(section).flatMap(({ line, statement }) => {
|
|
304
|
+
const path = statement ? readTomlAssignment(line)?.path : undefined;
|
|
305
|
+
if (!path) {
|
|
306
|
+
return [];
|
|
307
|
+
}
|
|
308
|
+
if (underParent) {
|
|
309
|
+
return [path[0]];
|
|
310
|
+
}
|
|
311
|
+
return path.length >= 2 && path[0] === parent ? [path[1]] : [];
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* How many of an owned section's lines are the writer's. Comments after its
|
|
317
|
+
* last key belong to what follows, so a note the user wrote above their own
|
|
318
|
+
* next table survives. `managedLines` are the writer's own comment lines,
|
|
319
|
+
* which are dropped wherever they sit.
|
|
320
|
+
*/
|
|
321
|
+
export function ownedLineCount(section, managedLines) {
|
|
322
|
+
const lastKey = section.lines.findLastIndex(isTomlContentLine);
|
|
323
|
+
const firstNote = section.lines.findIndex((line, index) => index > lastKey &&
|
|
324
|
+
line.trim().length > 0 &&
|
|
325
|
+
!managedLines.has(line.trim()));
|
|
326
|
+
return firstNote === -1 ? section.lines.length : firstNote;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* A table declared twice is a TOML error, and neither Grok nor Codex will
|
|
330
|
+
* start on one.
|
|
331
|
+
* Each `[[array]]` element opens a fresh scope for everything under it, so
|
|
332
|
+
* `[mcp_servers.env]` under two elements is not a repeat.
|
|
333
|
+
*/
|
|
334
|
+
export function findDuplicateTable(text) {
|
|
335
|
+
const tables = new Map();
|
|
336
|
+
const arrays = new Map();
|
|
337
|
+
const isUnder = (candidate, prefix) => candidate.length > prefix.length &&
|
|
338
|
+
prefix.every((part, index) => candidate[index] === part);
|
|
339
|
+
for (const { path, arrayTable } of splitTomlSections(text)) {
|
|
340
|
+
if (path === null) {
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
const key = JSON.stringify(path);
|
|
344
|
+
if (arrayTable) {
|
|
345
|
+
if (tables.has(key)) {
|
|
346
|
+
return path.join(".");
|
|
347
|
+
}
|
|
348
|
+
for (const scope of [tables, arrays]) {
|
|
349
|
+
for (const [seenKey, seenPath] of scope) {
|
|
350
|
+
if (isUnder(seenPath, path)) {
|
|
351
|
+
scope.delete(seenKey);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
arrays.set(key, path);
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (tables.has(key) || arrays.has(key)) {
|
|
359
|
+
return path.join(".");
|
|
360
|
+
}
|
|
361
|
+
tables.set(key, path);
|
|
362
|
+
}
|
|
363
|
+
return undefined;
|
|
364
|
+
}
|
|
365
|
+
//# sourceMappingURL=tomlSections.js.map
|
|
@@ -936,6 +936,7 @@ export class BaseProvider {
|
|
|
936
936
|
context: options.context,
|
|
937
937
|
csvOptions: options.csvOptions,
|
|
938
938
|
pdfOptions: options.pdfOptions,
|
|
939
|
+
videoOptions: options.videoOptions,
|
|
939
940
|
// Forward abort, tool filtering, and timeout options to prevent
|
|
940
941
|
// silent bypass when falling back from real streaming to fake streaming
|
|
941
942
|
abortSignal: options.abortSignal,
|
|
@@ -105,6 +105,10 @@ export class MessageBuilder {
|
|
|
105
105
|
csvOptions: options.csvOptions,
|
|
106
106
|
pdfOptions: options.pdfOptions,
|
|
107
107
|
imageOptions: options.imageOptions,
|
|
108
|
+
// Same reason as csvOptions: the keyframe budget and the
|
|
109
|
+
// transcription flag are read downstream by the detector, and
|
|
110
|
+
// a reconstruction that omits them silently restores defaults.
|
|
111
|
+
videoOptions: options.videoOptions,
|
|
108
112
|
provider: options.provider,
|
|
109
113
|
model: options.model,
|
|
110
114
|
temperature: options.temperature,
|
|
@@ -236,6 +240,10 @@ export class MessageBuilder {
|
|
|
236
240
|
csvOptions: options.csvOptions,
|
|
237
241
|
pdfOptions: options.pdfOptions,
|
|
238
242
|
imageOptions: options.imageOptions,
|
|
243
|
+
// Same reason as csvOptions: the keyframe budget and the
|
|
244
|
+
// transcription flag are read downstream by the detector, and
|
|
245
|
+
// a reconstruction that omits them silently restores defaults.
|
|
246
|
+
videoOptions: options.videoOptions,
|
|
239
247
|
provider: options.provider,
|
|
240
248
|
model: options.model,
|
|
241
249
|
temperature: options.temperature,
|
|
@@ -142,6 +142,47 @@ export declare class VideoProcessor extends BaseFileProcessor<ProcessedVideo> {
|
|
|
142
142
|
* falling back to the default when absent or non-numeric (#478).
|
|
143
143
|
*/
|
|
144
144
|
private static resolveFrameQuality;
|
|
145
|
+
/**
|
|
146
|
+
* Extract the clip's audio track and transcribe the speech in it (#433).
|
|
147
|
+
*
|
|
148
|
+
* ## Why this exists at all, now that Gemini hears video directly
|
|
149
|
+
*
|
|
150
|
+
* It does not help Gemini — a native provider receives the clip and its
|
|
151
|
+
* audio together. It is for everyone else. A provider on the frame path
|
|
152
|
+
* gets stills and nothing else, so a recorded standup, a support call or a
|
|
153
|
+
* narrated demo arrives with its entire spoken content missing, and
|
|
154
|
+
* "summarise this meeting" is answered from four screenshots.
|
|
155
|
+
*
|
|
156
|
+
* ## Why it is not a stub
|
|
157
|
+
*
|
|
158
|
+
* The issue asked for one, on the grounds that no transcription backend
|
|
159
|
+
* existed yet. One does: `AudioProcessor` has shipped Whisper transcription
|
|
160
|
+
* for standalone audio files for some time. A method that logged "not yet
|
|
161
|
+
* implemented" and returned undefined would be dead code sitting next to a
|
|
162
|
+
* working implementation of the same thing, and `--transcribe-audio` would
|
|
163
|
+
* still do nothing.
|
|
164
|
+
*
|
|
165
|
+
* The Whisper call is reproduced here rather than shared with
|
|
166
|
+
* `AudioProcessor`: extracting it into a common module is the better
|
|
167
|
+
* long-term shape, but that file is being edited concurrently, and a
|
|
168
|
+
* merge conflict in the audio pipeline is a worse outcome than fifty
|
|
169
|
+
* duplicated lines. The duplication is worth removing once both land.
|
|
170
|
+
*
|
|
171
|
+
* ## Failure behaviour
|
|
172
|
+
*
|
|
173
|
+
* Best-effort throughout, and never throws: a clip with no audio track, a
|
|
174
|
+
* machine without ffmpeg, a missing key, an oversized track or a failed
|
|
175
|
+
* request all return a *reason* and leave the rest of the pipeline intact.
|
|
176
|
+
* Each reason is distinct, because from the outside every one of them
|
|
177
|
+
* looks the same — no transcript — while the remedies differ completely.
|
|
178
|
+
*
|
|
179
|
+
* @param videoPath - Temp path to the video, already written by the caller
|
|
180
|
+
* @param tempDir - Caller-owned temp directory; removed in its `finally`
|
|
181
|
+
* @param metadata - Probed metadata, consulted for an audio track
|
|
182
|
+
* @param filename - Display name, for logs
|
|
183
|
+
* @param options - Caller settings; transcription runs only when asked
|
|
184
|
+
*/
|
|
185
|
+
private extractAndTranscribeAudio;
|
|
145
186
|
/**
|
|
146
187
|
* Extract keyframes from a video at calculated intervals.
|
|
147
188
|
*
|
|
@@ -166,18 +207,28 @@ export declare class VideoProcessor extends BaseFileProcessor<ProcessedVideo> {
|
|
|
166
207
|
* @param tempDir - Temp directory for frame output
|
|
167
208
|
* @param durationSec - Video duration in seconds
|
|
168
209
|
* @param options - Caller frame budget / encoder settings
|
|
169
|
-
* @returns
|
|
210
|
+
* @returns Each kept frame with the second it was sampled at, ascending
|
|
170
211
|
*/
|
|
171
212
|
private extractKeyframes;
|
|
172
213
|
/**
|
|
173
214
|
* Run ffmpeg to extract frames at specified timestamps.
|
|
174
215
|
*
|
|
175
|
-
* Uses the `-vf select` filter to pick frames at
|
|
176
|
-
* which is more efficient than seeking for each frame individually.
|
|
216
|
+
* Uses the `-vf select` filter to pick frames at least `intervalSec` apart,
|
|
217
|
+
* which is more efficient than seeking for each frame individually. This is
|
|
218
|
+
* a minimum-gap filter, not a seek to an exact time: on a source whose real
|
|
219
|
+
* frame spacing exceeds the requested interval, the frame actually selected
|
|
220
|
+
* can land well after the ideal schedule. A `showinfo` stage between
|
|
221
|
+
* `select` and `scale` reports each selected frame's true `pts_time` to
|
|
222
|
+
* stderr so the caller can label frames with when they were really
|
|
223
|
+
* sampled, not the idealized request.
|
|
177
224
|
*
|
|
178
225
|
* @param videoPath - Path to the video file
|
|
179
226
|
* @param outputDir - Directory to write frame files
|
|
180
227
|
* @param timestamps - Array of timestamps in seconds
|
|
228
|
+
* @param intervalSec - Minimum gap enforced between selected frames
|
|
229
|
+
* @returns The real `pts_time` (seconds) ffmpeg reported for each selected
|
|
230
|
+
* frame, in output-file order; may be shorter than `timestamps` if fewer
|
|
231
|
+
* real frames were available to select
|
|
181
232
|
*/
|
|
182
233
|
private runFfmpegFrameExtraction;
|
|
183
234
|
/**
|
|
@@ -217,9 +268,10 @@ export declare class VideoProcessor extends BaseFileProcessor<ProcessedVideo> {
|
|
|
217
268
|
* - Subtitle text (if available)
|
|
218
269
|
*
|
|
219
270
|
* @param metadata - Extracted video metadata
|
|
220
|
-
* @param
|
|
271
|
+
* @param keyframeTimestampsSec - The real second each kept keyframe was sampled at
|
|
221
272
|
* @param subtitleText - Extracted subtitle text (if any)
|
|
222
273
|
* @param filename - Original filename
|
|
274
|
+
* @param transcript - Machine transcription of the spoken audio (if requested and available)
|
|
223
275
|
* @returns Formatted text content for the LLM
|
|
224
276
|
*/
|
|
225
277
|
private buildTextContent;
|