vanspod 0.0.0-stage → 0.1.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.
Files changed (79) hide show
  1. package/README.md +74 -2
  2. package/bin/register.cjs +11 -0
  3. package/bin/vanspod.cjs +2 -0
  4. package/dist/adapterSettings.js +37 -0
  5. package/dist/adapters/chat-output.js +69 -0
  6. package/dist/adapters/chat-stream.js +44 -0
  7. package/dist/adapters/chat.js +63 -0
  8. package/dist/adapters/claude-reasoning.js +118 -0
  9. package/dist/adapters/claude-request.js +190 -0
  10. package/dist/adapters/claude-response.js +79 -0
  11. package/dist/adapters/index.js +19 -0
  12. package/dist/adapters/messages-stream.js +126 -0
  13. package/dist/adapters/messages.js +21 -0
  14. package/dist/adapters/responses-stream.js +70 -0
  15. package/dist/adapters/responses.js +28 -0
  16. package/dist/adapters/stream.js +99 -0
  17. package/dist/adapters/transport.js +26 -0
  18. package/dist/adapters/types.js +35 -0
  19. package/dist/appearance.js +10 -0
  20. package/dist/classroomApp.js +1814 -0
  21. package/dist/classroomUpdate.js +155 -0
  22. package/dist/claudeWebSearch.js +248 -0
  23. package/dist/clientDetection.js +35 -0
  24. package/dist/clientIcons.js +33 -0
  25. package/dist/clientRouteRuntime.js +149 -0
  26. package/dist/clientRoutes.js +118 -0
  27. package/dist/clientSettingsBackup.js +110 -0
  28. package/dist/clientTraffic.js +26 -0
  29. package/dist/clipboard.js +32 -0
  30. package/dist/codexWebSearch.js +80 -0
  31. package/dist/codexWire/apply-patch-envelope.js +139 -0
  32. package/dist/codexWire/chat-options.js +51 -0
  33. package/dist/codexWire/chat-tools.js +80 -0
  34. package/dist/codexWire/freeform-wrapper-scan.js +326 -0
  35. package/dist/codexWire/progressive-freeform-input.js +133 -0
  36. package/dist/codexWire/request.js +73 -0
  37. package/dist/codexWire/responses-tools.js +88 -0
  38. package/dist/codexWire/tool-arguments.js +39 -0
  39. package/dist/codexWire/tool-guidance.js +77 -0
  40. package/dist/commandRunner.js +100 -0
  41. package/dist/connectionMascot.js +235 -0
  42. package/dist/courseCatalog.js +131 -0
  43. package/dist/desktop.js +25 -0
  44. package/dist/entry.js +17 -0
  45. package/dist/environmentIcons.js +11 -0
  46. package/dist/exitGuard.js +17 -0
  47. package/dist/folderPicker.js +35 -0
  48. package/dist/installProgress.js +171 -0
  49. package/dist/installations.js +72 -0
  50. package/dist/instance.js +160 -0
  51. package/dist/launcher.js +155 -0
  52. package/dist/legacyMigration.js +87 -0
  53. package/dist/lifecyclePage.js +41 -0
  54. package/dist/modelProxy.js +105 -0
  55. package/dist/modelStatus.js +42 -0
  56. package/dist/page.js +1016 -0
  57. package/dist/pagePrototype.js +528 -0
  58. package/dist/platformStorage.js +91 -0
  59. package/dist/removal.js +41 -0
  60. package/dist/responsesChat.js +835 -0
  61. package/dist/routeFiles.js +646 -0
  62. package/dist/routerSessionModels.js +32 -0
  63. package/dist/runtimeOptions.js +14 -0
  64. package/dist/secureStorage.js +63 -0
  65. package/dist/settingsActions.js +100 -0
  66. package/dist/start.js +822 -0
  67. package/dist/storageErrors.js +17 -0
  68. package/dist/storageLocation.js +58 -0
  69. package/dist/version.js +4 -0
  70. package/dist/vsCodeRouteSettings.js +208 -0
  71. package/dist/webSearch.js +56 -0
  72. package/dist/windowsSecureStorage.js +163 -0
  73. package/media/brand-icon.ico +0 -0
  74. package/media/brand-logo.png +0 -0
  75. package/media/fadou-superhero-blink.png +0 -0
  76. package/media/fadou-superhero.png +0 -0
  77. package/package.json +27 -4
  78. package/scripts/tray.ps1 +40 -0
  79. package/scripts/uninstall.ps1 +133 -0
@@ -0,0 +1,326 @@
1
+ "use strict";
2
+ // Bounded classification of a PARTIAL freeform tool-call wrapper.
3
+ //
4
+ // `unwrapFreeformToolInput` decides the completed input with `JSON.parse`, which does not care
5
+ // what order an object's properties arrive in or how their names are spelled. The streaming side
6
+ // has to reach the same answer from a prefix, and it used to do that by comparing the buffer
7
+ // against the literal `{"input":"`. Spellings `JSON.parse` calls identical therefore matched no
8
+ // wrapper at all, streamed as raw JSON, and then completed as the unwrapped body:
9
+ // `{"metadata":1,"input":"cmd"}` and `{"\u0069nput":"cmd"}` both previewed the whole object and
10
+ // completed as `cmd` (#5151). #5047 and #5129 closed the compact and whitespace spellings of the
11
+ // same disagreement; reordering and escaping are the two the literal matcher could never see,
12
+ // because RFC 8259 objects are unordered and their names are strings with escapes.
13
+ //
14
+ // So the prefix is scanned as JSON instead of matched as text. The scan answers one question —
15
+ // which wrapper, if any, the completed text will unwrap to — and it answers it three ways:
16
+ //
17
+ // `input` the canonical key is present with a string value, whatever preceded it and however
18
+ // its name was spelled. Its value can be decoded progressively, because
19
+ // `unwrapFreeformToolInput` gives an own `input` precedence over every other property.
20
+ // `raw` no wrapper can apply, because the text is not an object or because it is one that
21
+ // `JSON.parse` will reject. Completion returns the buffer, so streaming it agrees.
22
+ // `hold` undecided. A key that has not arrived yet can still change the answer, so nothing
23
+ // is published until the object closes — where the scan itself already holds the
24
+ // member table completion's own rule consults, so no parse of the buffer is needed.
25
+ //
26
+ // The bound matters as much as the classification. A scan that walks the whole buffer on every
27
+ // delta is quadratic in the argument size, so classification gives up after
28
+ // `MAX_FREEFORM_WRAPPER_SCAN_CHARS` and holds. Giving up costs preview, never agreement: a held
29
+ // buffer is still resolved by the authoritative parse at completion.
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ exports.MAX_FREEFORM_WRAPPER_SCAN_CHARS = exports.JSON_ESCAPES = exports.JSON_WHITESPACE = void 0;
32
+ exports.scanFreeformWrapper = scanFreeformWrapper;
33
+ /** The insignificant whitespace `JSON.parse` accepts between tokens. */
34
+ exports.JSON_WHITESPACE = new Set([" ", "\t", "\n", "\r"]);
35
+ /** The two-character escapes JSON defines, and nothing else. */
36
+ exports.JSON_ESCAPES = new Map([
37
+ ['"', '"'], ["\\", "\\"], ["/", "/"],
38
+ ["b", "\b"], ["f", "\f"], ["n", "\n"], ["r", "\r"], ["t", "\t"],
39
+ ]);
40
+ /**
41
+ * How far a classification scan walks before it gives up and holds.
42
+ *
43
+ * A wrapper's own structure — its property names and the small scalar siblings a model puts
44
+ * beside the body — lives at the front of the object. A buffer still undecided after this much
45
+ * scanning is one whose preceding values are large, and holding it is what the routed
46
+ * restoration path already does for every unrecognized object.
47
+ */
48
+ exports.MAX_FREEFORM_WRAPPER_SCAN_CHARS = 4096;
49
+ const CANONICAL_KEY = "input";
50
+ /** The buffer ends inside a token: what follows can still change the answer. */
51
+ const HOLD = -1;
52
+ /** `JSON.parse` will reject this text no matter what is appended to it. */
53
+ const NEVER = -2;
54
+ /** Index of the first non-whitespace character at or after `from`, or HOLD past the end. */
55
+ function skipWhitespace(text, from) {
56
+ let i = from;
57
+ while (i < text.length && exports.JSON_WHITESPACE.has(text[i]))
58
+ i++;
59
+ return i < text.length ? i : HOLD;
60
+ }
61
+ /**
62
+ * Index just past the complete JSON string opening at `from`, or HOLD / NEVER.
63
+ *
64
+ * This reports a BOUNDARY where `decodeJsonStringPrefix` reports a decoded PREFIX: one has to
65
+ * fail on a truncated string and the other has to return what it decoded so far. They answer
66
+ * different questions about the same bytes and share one escape table so they cannot disagree
67
+ * about which escapes exist.
68
+ */
69
+ function scanString(text, from) {
70
+ let i = from + 1;
71
+ while (i < text.length) {
72
+ const c = text[i];
73
+ if (c === '"')
74
+ return i + 1;
75
+ if (c === "\\") {
76
+ const n = text[i + 1];
77
+ if (n === undefined)
78
+ return HOLD;
79
+ if (n === "u") {
80
+ const hex = text.slice(i + 2, i + 6);
81
+ if (hex.length < 4)
82
+ return HOLD;
83
+ if (!/^[0-9a-fA-F]{4}$/.test(hex))
84
+ return NEVER;
85
+ i += 6;
86
+ continue;
87
+ }
88
+ if (!exports.JSON_ESCAPES.has(n))
89
+ return NEVER;
90
+ i += 2;
91
+ continue;
92
+ }
93
+ // A literal control character is not legal inside a JSON string.
94
+ if (c.charCodeAt(0) <= 0x1f)
95
+ return NEVER;
96
+ i++;
97
+ }
98
+ return HOLD;
99
+ }
100
+ const JSON_NUMBER_CHARS = /[-+0-9.eE]/;
101
+ const JSON_NUMBER = /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?$/;
102
+ /** Index just past one complete scalar at `from`, or HOLD / NEVER. */
103
+ function scanScalar(text, from) {
104
+ const c = text[from];
105
+ if (c === '"')
106
+ return scanString(text, from);
107
+ if (c === "t" || c === "f" || c === "n") {
108
+ const literal = c === "t" ? "true" : c === "f" ? "false" : "null";
109
+ if (text.startsWith(literal, from))
110
+ return from + literal.length;
111
+ return literal.startsWith(text.slice(from)) ? HOLD : NEVER;
112
+ }
113
+ let end = from;
114
+ while (end < text.length && JSON_NUMBER_CHARS.test(text[end]))
115
+ end++;
116
+ if (end === from)
117
+ return NEVER;
118
+ // A number running to the end of the buffer can still grow another digit or exponent.
119
+ if (end >= text.length)
120
+ return HOLD;
121
+ return JSON_NUMBER.test(text.slice(from, end)) ? end : NEVER;
122
+ }
123
+ /** Index where the value of the member whose name opens at `from` begins, or HOLD / NEVER. */
124
+ function scanMemberKey(text, from) {
125
+ if (text[from] !== '"')
126
+ return NEVER;
127
+ const nameEnd = scanString(text, from);
128
+ if (nameEnd < 0)
129
+ return nameEnd;
130
+ const colon = skipWhitespace(text, nameEnd);
131
+ if (colon === HOLD)
132
+ return HOLD;
133
+ if (text[colon] !== ":")
134
+ return NEVER;
135
+ return skipWhitespace(text, colon + 1);
136
+ }
137
+ /**
138
+ * Index just past one complete JSON value at `from`, or HOLD / NEVER.
139
+ *
140
+ * Containers are walked with an explicit stack rather than recursion: the value being skipped
141
+ * is provider-controlled, and a deeply nested one must not be able to exhaust the call stack.
142
+ */
143
+ function scanValue(text, from) {
144
+ const closers = [];
145
+ let i = from;
146
+ for (;;) {
147
+ const c = text[i];
148
+ if (c === "{" || c === "[") {
149
+ const closer = c === "{" ? "}" : "]";
150
+ closers.push(closer);
151
+ const first = skipWhitespace(text, i + 1);
152
+ if (first === HOLD)
153
+ return HOLD;
154
+ if (text[first] === closer) {
155
+ closers.pop();
156
+ i = first + 1;
157
+ }
158
+ else if (closer === "}") {
159
+ const value = scanMemberKey(text, first);
160
+ if (value < 0)
161
+ return value;
162
+ i = value;
163
+ continue;
164
+ }
165
+ else {
166
+ i = first;
167
+ continue;
168
+ }
169
+ }
170
+ else {
171
+ const end = scanScalar(text, i);
172
+ if (end < 0)
173
+ return end;
174
+ i = end;
175
+ }
176
+ // One value is complete: close whatever it finished and find the next value, if any.
177
+ for (;;) {
178
+ if (closers.length === 0)
179
+ return i;
180
+ const at = skipWhitespace(text, i);
181
+ if (at === HOLD)
182
+ return HOLD;
183
+ const closer = closers[closers.length - 1];
184
+ if (text[at] === closer) {
185
+ closers.pop();
186
+ i = at + 1;
187
+ continue;
188
+ }
189
+ if (text[at] !== ",")
190
+ return NEVER;
191
+ const next = skipWhitespace(text, at + 1);
192
+ if (next === HOLD)
193
+ return HOLD;
194
+ if (closer === "}") {
195
+ const value = scanMemberKey(text, next);
196
+ if (value < 0)
197
+ return value;
198
+ i = value;
199
+ }
200
+ else {
201
+ i = next;
202
+ }
203
+ break;
204
+ }
205
+ }
206
+ }
207
+ /**
208
+ * Which wrapper the completed text will unwrap to, as far as this prefix can say.
209
+ *
210
+ * `fallbackKeys` is the tool's alternate-field vocabulary — `freeformFallbackKeys`, the same
211
+ * list `unwrapFreeformToolInput` filters against. No open prefix can decide a fallback key,
212
+ * because it unwraps only as the SINGLE string field and a second one can still arrive; so the
213
+ * scan keeps a table of the members it has already walked and consults it once, at the close.
214
+ */
215
+ function scanFreeformWrapper(text, fallbackKeys) {
216
+ // One clamp rather than a budget threaded through every helper. Every helper already holds
217
+ // when it runs off the end of what it can see, so a buffer whose classification needs more
218
+ // than this holds for exactly the right reason, and no scan can cost more than this many
219
+ // characters however large the arguments grow. Indices into the clamp are indices into the
220
+ // full text, because the clamp is a prefix of it.
221
+ const wholeText = text.length <= exports.MAX_FREEFORM_WRAPPER_SCAN_CHARS;
222
+ const bounded = wholeText ? text : text.slice(0, exports.MAX_FREEFORM_WRAPPER_SCAN_CHARS);
223
+ const hold = () => ({ kind: "hold" });
224
+ const open = skipWhitespace(bounded, 0);
225
+ if (open === HOLD)
226
+ return hold();
227
+ // Not an object, so no wrapper rule reaches it: arrays, scalars and ordinary bodies all
228
+ // complete as themselves.
229
+ if (bounded[open] !== "{")
230
+ return { kind: "raw" };
231
+ // Every top-level member is recorded as the scan passes it, last occurrence winning — the
232
+ // keep-last rule `JSON.parse` applies to duplicate keys. When the object closes, this table
233
+ // answers the only question completion asks of it — which members carried strings — so the
234
+ // closed object classifies directly instead of reparsing a buffer the scan just walked.
235
+ const members = new Map();
236
+ let i = open + 1;
237
+ for (;;) {
238
+ const at = skipWhitespace(bounded, i);
239
+ if (at === HOLD)
240
+ return hold();
241
+ if (bounded[at] === "}")
242
+ return afterTopLevelClose(bounded, at + 1, members, fallbackKeys, wholeText);
243
+ if (bounded[at] !== '"')
244
+ return { kind: "raw" };
245
+ const nameEnd = scanString(bounded, at);
246
+ if (nameEnd === HOLD)
247
+ return hold();
248
+ if (nameEnd === NEVER)
249
+ return { kind: "raw" };
250
+ let name;
251
+ try {
252
+ // The authoritative decoder for the name, so `{"\u0069nput":...}` resolves to the same
253
+ // key completion sees. Decoding it by hand here is the second implementation that would
254
+ // drift from `JSON.parse`, which is how this defect existed in the first place.
255
+ name = JSON.parse(bounded.slice(at, nameEnd));
256
+ }
257
+ catch {
258
+ return { kind: "raw" };
259
+ }
260
+ const colon = skipWhitespace(bounded, nameEnd);
261
+ if (colon === HOLD)
262
+ return hold();
263
+ if (bounded[colon] !== ":")
264
+ return { kind: "raw" };
265
+ const valueAt = skipWhitespace(bounded, colon + 1);
266
+ if (valueAt === HOLD)
267
+ return hold();
268
+ if (name === CANONICAL_KEY) {
269
+ // An own `input` wins over everything else in the object, so this is decidable now. A
270
+ // non-string value is decidable too, in the other direction: completion hands back the
271
+ // argument text unchanged rather than unwrapping a value it cannot use.
272
+ return bounded[valueAt] === '"'
273
+ ? { kind: "input", valueStart: valueAt + 1 }
274
+ : { kind: "raw" };
275
+ }
276
+ members.set(name, { stringValue: bounded[valueAt] === '"', valueStart: valueAt + 1 });
277
+ const valueEnd = scanValue(bounded, valueAt);
278
+ if (valueEnd === HOLD)
279
+ return hold();
280
+ if (valueEnd === NEVER)
281
+ return { kind: "raw" };
282
+ const next = skipWhitespace(bounded, valueEnd);
283
+ if (next === HOLD)
284
+ return hold();
285
+ if (bounded[next] === ",") {
286
+ // A comma promises another member, so the next non-whitespace character must open a
287
+ // member name. `{"code":"cmd",}` is not a completed object — `JSON.parse` rejects the
288
+ // trailing comma — and treating its `}` as the close would unwrap a preview completion
289
+ // hands back unchanged. Nested members already reject this through `scanMemberKey`;
290
+ // the top level has to ask the same question itself.
291
+ const member = skipWhitespace(bounded, next + 1);
292
+ if (member === HOLD)
293
+ return hold();
294
+ if (bounded[member] !== '"')
295
+ return { kind: "raw" };
296
+ i = member;
297
+ continue;
298
+ }
299
+ if (bounded[next] === "}")
300
+ return afterTopLevelClose(bounded, next + 1, members, fallbackKeys, wholeText);
301
+ return { kind: "raw" };
302
+ }
303
+ }
304
+ /**
305
+ * The object closed without a canonical key. Only whitespace may follow one that parses, so
306
+ * anything else makes the text raw. Whitespace to the end IS legal, but the member table
307
+ * already says which way completion goes on it: exactly one string-valued fallback field is
308
+ * the wrapper's value, and any other member shape streams the raw text — byte-exact through
309
+ * whatever trailing whitespace arrived, where a reparsed growing buffer was quadratic work on
310
+ * every whitespace delta and a held one swallowed the whitespace bytes entirely.
311
+ */
312
+ function afterTopLevelClose(text, from, members, fallbackKeys, wholeText) {
313
+ const trailing = skipWhitespace(text, from);
314
+ if (trailing !== HOLD)
315
+ return { kind: "raw" };
316
+ // A clamped prefix ends inside this whitespace, so what follows is unseen: hold rather than
317
+ // guess at a tail that could still invalidate the close. When the whole text fit, the object
318
+ // plus its trailing whitespace is complete — and its members decide it without a parse.
319
+ if (!wholeText)
320
+ return { kind: "hold" };
321
+ const candidates = fallbackKeys.filter(key => members.get(key)?.stringValue === true);
322
+ if (candidates.length === 1) {
323
+ return { kind: "input", valueStart: members.get(candidates[0]).valueStart };
324
+ }
325
+ return { kind: "raw" };
326
+ }
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.progressiveFreeformInput = progressiveFreeformInput;
4
+ const apply_patch_envelope_1 = require("./apply-patch-envelope");
5
+ const freeform_wrapper_scan_1 = require("./freeform-wrapper-scan");
6
+ /**
7
+ * Whether a body could still grow into one complete outer Markdown fence.
8
+ *
9
+ * `stripMarkdownCodeFence` removes such a fence at completion for exactly the two tools that
10
+ * own the grammar, so a fenced body's streamed bytes and its completed input disagree unless
11
+ * the stream holds. A buffer that does not open with a fence can never acquire one, so
12
+ * ordinary bodies are unaffected; a buffer that does keeps its preview suppressed for the
13
+ * whole call, because a closing fence can still be followed by more text that withdraws it.
14
+ */
15
+ function mayBecomeFencedBody(text, toolName) {
16
+ if (toolName !== "exec" && toolName !== "apply_patch")
17
+ return false;
18
+ const head = text.trimStart();
19
+ if (head === "")
20
+ return true;
21
+ return head.startsWith("```") || "```".startsWith(head);
22
+ }
23
+ const LOW_SURROGATE_ESCAPE = /^\\u[dD][c-fC-F][0-9a-fA-F]{2}$/;
24
+ /**
25
+ * The decoded prefix of a JSON string body, stopping at the first byte it cannot resolve.
26
+ *
27
+ * Every stop is a hold rather than a guess, because `JSON.parse` decides the completed value
28
+ * and anything invented here would be retracted. Three of them are not obvious:
29
+ *
30
+ * An escape JSON does not define makes the whole wrapper unparseable no matter what arrives
31
+ * next, so completion falls back to the raw text. Returning `null` for that case stops the
32
+ * preview rather than continuing to decode a value the completed item will never carry.
33
+ * `\\b` and `\\f` are defined, and were previously decoded to the letters b and f.
34
+ *
35
+ * A lone high surrogate is not a character. Emitting one alone puts an unpaired code unit in a
36
+ * delta the client has to decode by itself, so the pair is emitted together or not at all.
37
+ */
38
+ function decodeJsonStringPrefix(body) {
39
+ let out = "";
40
+ for (let i = 0; i < body.length; i++) {
41
+ const c = body[i];
42
+ if (c === '"')
43
+ break; // unescaped closing quote: value complete
44
+ if (c !== "\\") {
45
+ // A literal control character is not legal inside a JSON string, so `JSON.parse` will
46
+ // reject this wrapper and completion will return the raw text. Emitting the decoded value
47
+ // first is the disagreement this decoder exists to prevent, so stop instead.
48
+ if (c.charCodeAt(0) <= 0x1f)
49
+ return null;
50
+ out += c;
51
+ continue;
52
+ }
53
+ const n = body[i + 1];
54
+ if (n === undefined)
55
+ break; // escape split across chunks: wait for more
56
+ if (n === "u") {
57
+ const hex = body.slice(i + 2, i + 6);
58
+ if (hex.length < 4)
59
+ break; // split across chunks: wait for more
60
+ if (!/^[0-9a-fA-F]{4}$/.test(hex))
61
+ return null; // never parses
62
+ const code = parseInt(hex, 16);
63
+ if (code >= 0xd800 && code <= 0xdbff) {
64
+ const low = body.slice(i + 6, i + 12);
65
+ if (!LOW_SURROGATE_ESCAPE.test(low))
66
+ break;
67
+ out += String.fromCharCode(code, parseInt(low.slice(2), 16));
68
+ i += 11;
69
+ continue;
70
+ }
71
+ out += String.fromCharCode(code);
72
+ i += 5;
73
+ continue;
74
+ }
75
+ const escaped = freeform_wrapper_scan_1.JSON_ESCAPES.get(n);
76
+ if (escaped === undefined)
77
+ return null; // never parses
78
+ out += escaped;
79
+ i += 1;
80
+ }
81
+ return out;
82
+ }
83
+ /**
84
+ * The value to stream so far, or `null` to HOLD because nothing can be decided yet.
85
+ *
86
+ * `input` is decidable from its prefix: `unwrapFreeformToolInput` returns it whenever the
87
+ * key is present, whatever else the object carries, so its value can be unescaped
88
+ * progressively and never retracted.
89
+ *
90
+ * One case escapes that claim and is accepted rather than fixed: a duplicate `input` key.
91
+ * `JSON.parse` keeps the last one, so `{"input":"a","input":"b"}` streams a and completes with
92
+ * b. Closing it means holding every canonical wrapper until its object parses, which is the
93
+ * progressive streaming this path exists to provide. The completed item stays authoritative,
94
+ * and no model emits a duplicate key in practice.
95
+ *
96
+ * A wrapper that turns invalid AFTER streaming has committed has the same shape and the same
97
+ * answer. `{"input":"a` followed by `\\qb"}` has already published a when the undefined escape
98
+ * arrives, and completion returns the raw text because nothing parses. The preview stops
99
+ * there: no rewind, and no decoded text the completed item does not contain. Bounding the
100
+ * damage is what is available without giving up progressive streaming, and the args are
101
+ * unusable in that case whichever representation wins.
102
+ *
103
+ * A fallback key decides only at the object's close. It unwraps as the SINGLE string field,
104
+ * and a second key can still arrive while the object is open — so a value emitted early would
105
+ * have to be taken back. The scan therefore keeps a member table and consults it at the
106
+ * close, publishing the wrapped value (or the raw text) without ever reparsing the buffer.
107
+ * The routed passthrough in `responses-custom-tool-repair.ts` already holds any object prefix
108
+ * for the same reason (#5047).
109
+ *
110
+ * An object that has not reached a canonical key YET is in exactly that position, and used to
111
+ * be treated as raw because it did not match the literal `{"input":"`. It holds now: `input`
112
+ * can still arrive after other properties, or wearing an escaped spelling, and completion would
113
+ * then unwrap a body whose wrapper syntax had already been published as deltas (#5151). The
114
+ * cost of holding is preview on an object body that turns out not to be a wrapper; the cost of
115
+ * not holding was publishing bytes the completed item removes.
116
+ */
117
+ function progressiveFreeformInput(args, toolName) {
118
+ const scan = (0, freeform_wrapper_scan_1.scanFreeformWrapper)(args, (0, apply_patch_envelope_1.freeformFallbackKeys)(toolName));
119
+ if (scan.kind === "input") {
120
+ const decoded = decodeJsonStringPrefix(args.slice(scan.valueStart));
121
+ if (decoded === null)
122
+ return null;
123
+ return mayBecomeFencedBody(decoded, toolName) ? null : decoded;
124
+ }
125
+ if (scan.kind === "raw")
126
+ return mayBecomeFencedBody(args, toolName) ? null : args;
127
+ // Undecided: an incomplete object, or a close whose tail is clamped out of view. Both stay
128
+ // held — the first is provably incomplete, and re-walking a budget-exhausted buffer on every
129
+ // delta is work for a preview that arrives in the same instant as the authoritative
130
+ // completion behind it. A closed object the scan could see whole never reaches here: its
131
+ // member table already resolved it to `input` or `raw`.
132
+ return null;
133
+ }
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeCodexRequest = normalizeCodexRequest;
4
+ exports.codexResponsesUpstreamBody = codexResponsesUpstreamBody;
5
+ const tool_guidance_1 = require("./tool-guidance");
6
+ const types_1 = require("../adapters/types");
7
+ /** Move Codex's inline tool catalog into the standard Responses tools field. */
8
+ function normalizeCodexRequest(body) {
9
+ if (!Array.isArray(body.input))
10
+ return body;
11
+ const tools = body.tools === undefined ? [] : [...(0, types_1.list)(body.tools, "tools")];
12
+ const input = [];
13
+ let changed = false;
14
+ for (const item of body.input) {
15
+ if (!item || typeof item !== "object" || item.type !== "additional_tools") {
16
+ input.push(item);
17
+ continue;
18
+ }
19
+ const group = (0, types_1.object)(item, "additional_tools");
20
+ (0, types_1.allowedFields)(group, ["type", "id", "role", "tools"], "additional_tools");
21
+ tools.push(...(0, types_1.list)(group.tools, "additional_tools.tools"));
22
+ changed = true;
23
+ }
24
+ return changed ? { ...body, input, tools } : body;
25
+ }
26
+ /** Router's Responses input accepts function history, so lower freeform tools
27
+ * consistently across declarations, selectors, calls and paired results. */
28
+ function codexResponsesUpstreamBody(body) {
29
+ // This Codex private account selector is not part of the Router's Responses
30
+ // contract. Other unknown fields remain intact for upstream extensions.
31
+ const { access_programs: _accessPrograms, ...publicBody } = body;
32
+ const guidance = (0, tool_guidance_1.codexToolGuidance)(body);
33
+ const tool = (value, namespace) => {
34
+ const record = (0, types_1.object)(value, "tool");
35
+ if (record.type === "namespace")
36
+ return { ...record, tools: (0, types_1.list)(record.tools, "namespace tools").map(value => tool(value, String(record.name))) };
37
+ if (record.type !== "custom")
38
+ return record;
39
+ const { format, ...rest } = record;
40
+ const codeMode = guidance.codeMode && record.name === "exec" && (namespace === undefined || namespace === "functions");
41
+ const description = `Raw input for ${String(record.name)}.${format === undefined ? "" : ` The input must follow this format definition: ${JSON.stringify(format)}`}`;
42
+ return { ...rest, type: "function", parameters: { type: "object", properties: { input: {
43
+ type: "string", description: codeMode ? (0, tool_guidance_1.codeModeInputDescription)(description) : description,
44
+ } }, required: ["input"], additionalProperties: false } };
45
+ };
46
+ const choice = (value) => {
47
+ if (!value || typeof value !== "object")
48
+ return value;
49
+ const record = (0, types_1.object)(value, "tool_choice");
50
+ if (record.type === "custom")
51
+ return { ...record, type: "function" };
52
+ if (record.type === "allowed_tools")
53
+ return { ...record, tools: (0, types_1.list)(record.tools, "allowed tools").map(choice) };
54
+ return record;
55
+ };
56
+ return { ...publicBody,
57
+ ...(guidance.codeMode ? { instructions: (0, tool_guidance_1.appendToolInstructions)(typeof body.instructions === "string" ? body.instructions : "", [tool_guidance_1.CODE_MODE_RESULT_ECHO_SENTENCE, tool_guidance_1.CODE_MODE_HOST_CONTRACT_SENTENCE]) } : {}),
58
+ ...(body.tools === undefined ? {} : { tools: (0, types_1.list)(body.tools, "tools").map(value => tool(value)) }),
59
+ ...(body.tool_choice === undefined ? {} : { tool_choice: choice(body.tool_choice) }),
60
+ ...(Array.isArray(body.input) ? { input: body.input.map(value => {
61
+ const item = (0, types_1.object)(value, "input item");
62
+ if (item.type === "custom_tool_call") {
63
+ const { input, id: _id, ...rest } = item;
64
+ return { ...rest, type: "function_call", arguments: JSON.stringify({ input: (0, types_1.string)(input, "custom tool input") }) };
65
+ }
66
+ if (item.type === "custom_tool_call_output") {
67
+ const { id: _id, ...rest } = item;
68
+ return { ...rest, type: "function_call_output" };
69
+ }
70
+ return item;
71
+ }) } : {}),
72
+ };
73
+ }
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CodexResponsesTools = void 0;
4
+ const apply_patch_envelope_1 = require("./apply-patch-envelope");
5
+ const types_1 = require("../adapters/types");
6
+ /** Restore only tools explicitly declared as freeform in the client's request. */
7
+ class CodexResponsesTools {
8
+ tools = [];
9
+ calls = new Map();
10
+ constructor(body) {
11
+ const collect = (tools, namespace) => {
12
+ for (const raw of tools) {
13
+ const tool = (0, types_1.object)(raw, "tool");
14
+ if (tool.type === "namespace")
15
+ collect((0, types_1.list)(tool.tools, "namespace tools"), (0, types_1.string)(tool.name, "namespace"));
16
+ else if (tool.type === "function" || tool.type === "custom") {
17
+ const name = (0, types_1.string)(tool.name, "tool name");
18
+ if (this.tools.some(tool => tool.name === name && tool.namespace === namespace))
19
+ throw new types_1.AdapterError("Responses 工具宣告的 namespace/name 識別重複。");
20
+ this.tools.push({ name, namespace, custom: tool.type === "custom" });
21
+ }
22
+ }
23
+ };
24
+ if (body.tools !== undefined)
25
+ collect((0, types_1.list)(body.tools, "tools"));
26
+ }
27
+ get hasCustomTools() { return this.tools.some(tool => tool.custom); }
28
+ identity(item) {
29
+ if ((item.type !== "function_call" && item.type !== "custom_tool_call") || typeof item.name !== "string")
30
+ return;
31
+ let matches = this.tools.filter(tool => tool.name === item.name && tool.namespace === item.namespace);
32
+ if (!matches.length && item.namespace === undefined)
33
+ matches = this.tools.filter(tool => tool.name === item.name || (tool.namespace && `${tool.namespace}.${tool.name}` === item.name));
34
+ if (matches.length > 1)
35
+ throw new types_1.AdapterError("上游工具識別不明確;缺少可唯一判定的 namespace。", 502);
36
+ return matches.length === 1 && matches[0].custom ? matches[0] : undefined;
37
+ }
38
+ convertItem(value) {
39
+ const item = (0, types_1.object)(value, "Responses output item");
40
+ const tool = this.identity(item);
41
+ if (!tool)
42
+ return item;
43
+ const { arguments: args, input, ...rest } = item;
44
+ return { ...rest, type: "custom_tool_call", name: tool.name,
45
+ ...(tool.namespace ? { namespace: tool.namespace } : {}), input: (0, apply_patch_envelope_1.repairFreeformToolInput)((0, types_1.string)(item.type === "custom_tool_call" ? input : args, "tool input"), tool.name, tool.namespace) };
46
+ }
47
+ response(body) {
48
+ return Array.isArray(body.output) ? { ...body, output: body.output.map(item => this.convertItem(item)) } : body;
49
+ }
50
+ events(data) {
51
+ if (data.type === "response.output_item.added") {
52
+ const item = (0, types_1.object)(data.item, "output item");
53
+ if (this.identity(item)) {
54
+ this.calls.set((0, types_1.string)(item.id, "item id"), { item, args: typeof (item.arguments ?? item.input) === "string" ? String(item.arguments ?? item.input) : "", finished: false });
55
+ return [{ ...data, item: this.convertItem({ ...item, arguments: "", input: "" }) }];
56
+ }
57
+ }
58
+ const state = typeof data.item_id === "string" ? this.calls.get(data.item_id) : undefined;
59
+ if (state && (data.type === "response.function_call_arguments.delta" || data.type === "response.custom_tool_call_input.delta")) {
60
+ state.args += (0, types_1.string)(data.delta, "arguments delta");
61
+ return []; // Hold the JSON wrapper until the complete freeform input is known.
62
+ }
63
+ if (state && (data.type === "response.function_call_arguments.done" || data.type === "response.custom_tool_call_input.done")) {
64
+ const input = this.convertItem({ ...state.item, arguments: data.arguments ?? state.args, input: data.input ?? state.args }).input;
65
+ state.finished = true;
66
+ const { arguments: _args, input: _input, ...rest } = data;
67
+ return [{ ...rest, type: "response.custom_tool_call_input.delta", delta: input },
68
+ { ...rest, type: "response.custom_tool_call_input.done", input }];
69
+ }
70
+ if (data.type === "response.output_item.done") {
71
+ const item = (0, types_1.object)(data.item, "output item"), converted = this.convertItem(item);
72
+ if (converted !== item) {
73
+ const state = typeof item.id === "string" ? this.calls.get(item.id) : undefined;
74
+ const prefix = state?.finished ? [] : [
75
+ { type: "response.custom_tool_call_input.delta", item_id: item.id, output_index: data.output_index, delta: converted.input },
76
+ { type: "response.custom_tool_call_input.done", item_id: item.id, output_index: data.output_index, input: converted.input },
77
+ ];
78
+ if (state)
79
+ state.finished = true;
80
+ return [...prefix, { ...data, item: converted }];
81
+ }
82
+ }
83
+ if ((data.type === "response.completed" || data.type === "response.incomplete") && data.response)
84
+ return [{ ...data, response: this.response((0, types_1.object)(data.response, "response")) }];
85
+ return [data];
86
+ }
87
+ }
88
+ exports.CodexResponsesTools = CodexResponsesTools;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toolCallArgumentsUsable = toolCallArgumentsUsable;
4
+ exports.toolCallArgumentsCouldBeJson = toolCallArgumentsCouldBeJson;
5
+ /**
6
+ * Whether assembled function-call arguments are usable JSON.
7
+ * An empty buffer is valid (no-arg tools send no deltas). Non-empty must parse.
8
+ */
9
+ function toolCallArgumentsUsable(args) {
10
+ if (args.length === 0)
11
+ return true;
12
+ const trimmed = args.trim();
13
+ if (!trimmed)
14
+ return false;
15
+ try {
16
+ JSON.parse(args);
17
+ return true;
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ }
23
+ /**
24
+ * Whether an in-progress function-call argument buffer could still become valid JSON.
25
+ * The first non-whitespace byte must be one that can begin a JSON value.
26
+ */
27
+ function toolCallArgumentsCouldBeJson(args) {
28
+ const first = args.trimStart().charAt(0);
29
+ if (first === "")
30
+ return true;
31
+ return first === "{"
32
+ || first === "["
33
+ || first === "\""
34
+ || first === "-"
35
+ || (first >= "0" && first <= "9")
36
+ || first === "t"
37
+ || first === "f"
38
+ || first === "n";
39
+ }