@jxburros/ai-nugget 0.4.1 → 0.7.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 (187) hide show
  1. package/README.md +158 -382
  2. package/UPGRADING.md +182 -0
  3. package/dist/adapters/engines/anthropic.d.ts +7 -1
  4. package/dist/adapters/engines/anthropic.js +82 -37
  5. package/dist/adapters/engines/base.d.ts +29 -2
  6. package/dist/adapters/engines/base.js +52 -8
  7. package/dist/adapters/engines/google.d.ts +7 -1
  8. package/dist/adapters/engines/google.js +80 -29
  9. package/dist/adapters/engines/ollama.d.ts +2 -1
  10. package/dist/adapters/engines/ollama.js +57 -15
  11. package/dist/adapters/engines/openaiChat.d.ts +8 -1
  12. package/dist/adapters/engines/openaiChat.js +109 -29
  13. package/dist/adapters/profiles.d.ts +21 -1
  14. package/dist/adapters/profiles.js +12 -2
  15. package/dist/agent/loop.d.ts +45 -1
  16. package/dist/agent/loop.js +117 -12
  17. package/dist/cjs/adapters/engines/anthropic.js +326 -0
  18. package/dist/cjs/adapters/engines/base.js +147 -0
  19. package/dist/cjs/adapters/engines/google.js +291 -0
  20. package/dist/cjs/adapters/engines/ollama.js +205 -0
  21. package/dist/cjs/adapters/engines/openaiChat.js +312 -0
  22. package/dist/cjs/adapters/index.js +22 -0
  23. package/dist/cjs/adapters/profiles.js +125 -0
  24. package/dist/cjs/agent/index.js +18 -0
  25. package/dist/cjs/agent/loop.js +373 -0
  26. package/dist/cjs/agent/tools.js +51 -0
  27. package/dist/cjs/connect.js +27 -0
  28. package/dist/cjs/errors.js +110 -0
  29. package/dist/cjs/handler.js +586 -0
  30. package/dist/cjs/index.js +28 -0
  31. package/dist/cjs/json.js +121 -0
  32. package/dist/cjs/keys.js +81 -0
  33. package/dist/cjs/package.json +3 -0
  34. package/dist/cjs/policy.js +42 -0
  35. package/dist/cjs/reasoning.js +159 -0
  36. package/dist/cjs/redact.js +78 -0
  37. package/dist/cjs/tokens.js +22 -0
  38. package/dist/cjs/transport.js +172 -0
  39. package/dist/cjs/types.js +6 -0
  40. package/dist/cjs/util.js +126 -0
  41. package/dist/errors.js +25 -1
  42. package/dist/handler.d.ts +67 -1
  43. package/dist/handler.js +266 -61
  44. package/dist/index.d.ts +1 -0
  45. package/dist/index.js +1 -0
  46. package/dist/reasoning.d.ts +45 -0
  47. package/dist/reasoning.js +154 -0
  48. package/dist/redact.d.ts +18 -0
  49. package/dist/redact.js +29 -1
  50. package/dist/transport.d.ts +3 -1
  51. package/dist/transport.js +21 -1
  52. package/dist/types.d.ts +112 -2
  53. package/dist/util.d.ts +22 -0
  54. package/dist/util.js +59 -0
  55. package/nugget/VERSION.txt +2 -2
  56. package/nugget/dist/adapters/engines/anthropic.d.ts +14 -0
  57. package/nugget/dist/adapters/engines/anthropic.d.ts.map +1 -0
  58. package/nugget/dist/adapters/engines/anthropic.js +323 -0
  59. package/nugget/dist/adapters/engines/anthropic.js.map +1 -0
  60. package/nugget/dist/adapters/engines/base.d.ts +50 -0
  61. package/nugget/dist/adapters/engines/base.d.ts.map +1 -0
  62. package/nugget/dist/adapters/engines/base.js +137 -0
  63. package/nugget/dist/adapters/engines/base.js.map +1 -0
  64. package/nugget/dist/adapters/engines/google.d.ts +24 -0
  65. package/nugget/dist/adapters/engines/google.d.ts.map +1 -0
  66. package/nugget/dist/adapters/engines/google.js +288 -0
  67. package/nugget/dist/adapters/engines/google.js.map +1 -0
  68. package/nugget/dist/adapters/engines/ollama.d.ts +11 -0
  69. package/nugget/dist/adapters/engines/ollama.d.ts.map +1 -0
  70. package/nugget/dist/adapters/engines/ollama.js +202 -0
  71. package/nugget/dist/adapters/engines/ollama.js.map +1 -0
  72. package/nugget/dist/adapters/engines/openaiChat.d.ts +22 -0
  73. package/nugget/dist/adapters/engines/openaiChat.d.ts.map +1 -0
  74. package/nugget/dist/adapters/engines/openaiChat.js +308 -0
  75. package/nugget/dist/adapters/engines/openaiChat.js.map +1 -0
  76. package/nugget/dist/adapters/index.d.ts +5 -0
  77. package/nugget/dist/adapters/index.d.ts.map +1 -0
  78. package/nugget/dist/adapters/index.js +17 -0
  79. package/nugget/dist/adapters/index.js.map +1 -0
  80. package/nugget/dist/adapters/profiles.d.ts +62 -0
  81. package/nugget/dist/adapters/profiles.d.ts.map +1 -0
  82. package/nugget/dist/adapters/profiles.js +121 -0
  83. package/nugget/dist/adapters/profiles.js.map +1 -0
  84. package/nugget/dist/agent/index.d.ts +3 -0
  85. package/nugget/dist/agent/index.d.ts.map +1 -0
  86. package/nugget/dist/agent/index.js +3 -0
  87. package/nugget/dist/agent/index.js.map +1 -0
  88. package/nugget/dist/agent/loop.d.ts +114 -0
  89. package/nugget/dist/agent/loop.d.ts.map +1 -0
  90. package/nugget/dist/agent/loop.js +371 -0
  91. package/nugget/dist/agent/loop.js.map +1 -0
  92. package/nugget/dist/agent/tools.d.ts +29 -0
  93. package/nugget/dist/agent/tools.d.ts.map +1 -0
  94. package/nugget/dist/agent/tools.js +48 -0
  95. package/nugget/dist/agent/tools.js.map +1 -0
  96. package/nugget/dist/cjs/adapters/engines/anthropic.js +326 -0
  97. package/nugget/dist/cjs/adapters/engines/base.js +147 -0
  98. package/nugget/dist/cjs/adapters/engines/google.js +291 -0
  99. package/nugget/dist/cjs/adapters/engines/ollama.js +205 -0
  100. package/nugget/dist/cjs/adapters/engines/openaiChat.js +312 -0
  101. package/nugget/dist/cjs/adapters/index.js +22 -0
  102. package/nugget/dist/cjs/adapters/profiles.js +125 -0
  103. package/nugget/dist/cjs/agent/index.js +18 -0
  104. package/nugget/dist/cjs/agent/loop.js +373 -0
  105. package/nugget/dist/cjs/agent/tools.js +51 -0
  106. package/nugget/dist/cjs/connect.js +27 -0
  107. package/nugget/dist/cjs/errors.js +110 -0
  108. package/nugget/dist/cjs/handler.js +586 -0
  109. package/nugget/dist/cjs/index.js +28 -0
  110. package/nugget/dist/cjs/json.js +121 -0
  111. package/nugget/dist/cjs/keys.js +81 -0
  112. package/nugget/dist/cjs/package.json +3 -0
  113. package/nugget/dist/cjs/policy.js +42 -0
  114. package/nugget/dist/cjs/reasoning.js +159 -0
  115. package/nugget/dist/cjs/redact.js +78 -0
  116. package/nugget/dist/cjs/tokens.js +22 -0
  117. package/nugget/dist/cjs/transport.js +172 -0
  118. package/nugget/dist/cjs/types.js +6 -0
  119. package/nugget/dist/cjs/util.js +126 -0
  120. package/nugget/dist/connect.d.ts +31 -0
  121. package/nugget/dist/connect.d.ts.map +1 -0
  122. package/nugget/dist/connect.js +25 -0
  123. package/nugget/dist/connect.js.map +1 -0
  124. package/nugget/dist/errors.d.ts +22 -0
  125. package/nugget/dist/errors.d.ts.map +1 -0
  126. package/nugget/dist/errors.js +104 -0
  127. package/nugget/dist/errors.js.map +1 -0
  128. package/nugget/dist/handler.d.ts +114 -0
  129. package/nugget/dist/handler.d.ts.map +1 -0
  130. package/nugget/dist/handler.js +582 -0
  131. package/nugget/dist/handler.js.map +1 -0
  132. package/nugget/dist/index.d.ts +13 -0
  133. package/nugget/dist/index.d.ts.map +1 -0
  134. package/nugget/dist/index.js +13 -0
  135. package/nugget/dist/index.js.map +1 -0
  136. package/nugget/dist/json.d.ts +7 -0
  137. package/nugget/dist/json.d.ts.map +1 -0
  138. package/nugget/dist/json.js +114 -0
  139. package/nugget/dist/json.js.map +1 -0
  140. package/nugget/dist/keys.d.ts +7 -0
  141. package/nugget/dist/keys.d.ts.map +1 -0
  142. package/nugget/dist/keys.js +75 -0
  143. package/nugget/dist/keys.js.map +1 -0
  144. package/nugget/dist/policy.d.ts +6 -0
  145. package/nugget/dist/policy.d.ts.map +1 -0
  146. package/nugget/dist/policy.js +37 -0
  147. package/nugget/dist/policy.js.map +1 -0
  148. package/nugget/dist/reasoning.d.ts +45 -0
  149. package/nugget/dist/reasoning.d.ts.map +1 -0
  150. package/nugget/dist/reasoning.js +154 -0
  151. package/nugget/dist/reasoning.js.map +1 -0
  152. package/nugget/dist/redact.d.ts +26 -0
  153. package/nugget/dist/redact.d.ts.map +1 -0
  154. package/nugget/dist/redact.js +74 -0
  155. package/nugget/dist/redact.js.map +1 -0
  156. package/nugget/dist/tokens.d.ts +5 -0
  157. package/nugget/dist/tokens.d.ts.map +1 -0
  158. package/nugget/dist/tokens.js +18 -0
  159. package/nugget/dist/tokens.js.map +1 -0
  160. package/nugget/dist/transport.d.ts +30 -0
  161. package/nugget/dist/transport.d.ts.map +1 -0
  162. package/nugget/dist/transport.js +165 -0
  163. package/nugget/dist/transport.js.map +1 -0
  164. package/nugget/dist/types.d.ts +301 -0
  165. package/nugget/dist/types.d.ts.map +1 -0
  166. package/nugget/dist/types.js +4 -0
  167. package/nugget/dist/types.js.map +1 -0
  168. package/nugget/dist/util.d.ts +31 -0
  169. package/nugget/dist/util.d.ts.map +1 -0
  170. package/nugget/dist/util.js +114 -0
  171. package/nugget/dist/util.js.map +1 -0
  172. package/nugget/src/adapters/engines/anthropic.ts +78 -37
  173. package/nugget/src/adapters/engines/base.ts +50 -7
  174. package/nugget/src/adapters/engines/google.ts +79 -29
  175. package/nugget/src/adapters/engines/ollama.ts +56 -17
  176. package/nugget/src/adapters/engines/openaiChat.ts +104 -30
  177. package/nugget/src/adapters/profiles.ts +53 -3
  178. package/nugget/src/agent/loop.ts +156 -13
  179. package/nugget/src/errors.ts +25 -1
  180. package/nugget/src/handler.ts +308 -59
  181. package/nugget/src/index.ts +1 -0
  182. package/nugget/src/reasoning.ts +174 -0
  183. package/nugget/src/redact.ts +32 -1
  184. package/nugget/src/transport.ts +20 -1
  185. package/nugget/src/types.ts +113 -1
  186. package/nugget/src/util.ts +64 -0
  187. package/package.json +18 -5
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractJson = extractJson;
4
+ exports.extractJsonWithSchema = extractJsonWithSchema;
5
+ exports.requireString = requireString;
6
+ exports.requireNumber = requireNumber;
7
+ exports.requireStringArray = requireStringArray;
8
+ exports.requireOptionalString = requireOptionalString;
9
+ const errors_js_1 = require("./errors.js");
10
+ function extractJson(text) {
11
+ const trimmed = text.trim();
12
+ if (!trimmed)
13
+ return null;
14
+ const direct = tryParse(trimmed);
15
+ if (direct.ok)
16
+ return direct.value;
17
+ const fenced = /```(?:json)?\s*([\s\S]*?)```/i.exec(text);
18
+ if (fenced?.[1]) {
19
+ const parsed = tryParse(fenced[1].trim());
20
+ if (parsed.ok)
21
+ return parsed.value;
22
+ }
23
+ for (const [open, close] of [['{', '}'], ['[', ']']]) {
24
+ for (const span of balancedSpans(text, open, close)) {
25
+ const parsed = tryParse(span);
26
+ if (parsed.ok)
27
+ return parsed.value;
28
+ }
29
+ }
30
+ return null;
31
+ }
32
+ /**
33
+ * Yields each top-level `open`/`close` balanced substring of `text`, tracking
34
+ * nesting depth and skipping over string-literal content (including escapes)
35
+ * so a stray brace inside a string or trailing prose can't prematurely close
36
+ * or extend a span. Used instead of first-`indexOf`/last-`lastIndexOf` so
37
+ * multiple JSON regions (or JSON followed by unrelated braces) don't get
38
+ * spliced into one bogus span.
39
+ */
40
+ function* balancedSpans(text, open, close) {
41
+ let depth = 0;
42
+ let spanStart = -1;
43
+ let inString = false;
44
+ let escaped = false;
45
+ for (let i = 0; i < text.length; i += 1) {
46
+ const ch = text[i];
47
+ if (inString) {
48
+ if (escaped)
49
+ escaped = false;
50
+ else if (ch === '\\')
51
+ escaped = true;
52
+ else if (ch === '"')
53
+ inString = false;
54
+ continue;
55
+ }
56
+ if (ch === '"') {
57
+ inString = true;
58
+ continue;
59
+ }
60
+ if (ch === open) {
61
+ if (depth === 0)
62
+ spanStart = i;
63
+ depth += 1;
64
+ continue;
65
+ }
66
+ if (ch === close && depth > 0) {
67
+ depth -= 1;
68
+ if (depth === 0 && spanStart >= 0) {
69
+ yield text.slice(spanStart, i + 1);
70
+ spanStart = -1;
71
+ }
72
+ }
73
+ }
74
+ }
75
+ function extractJsonWithSchema(text, parse) {
76
+ const value = extractJson(text);
77
+ if (value === null)
78
+ throw new errors_js_1.AIError('No JSON object or array found in model output', { kind: 'invalid_response' });
79
+ try {
80
+ return parse(value);
81
+ }
82
+ catch (error) {
83
+ const message = error instanceof Error ? error.message : 'JSON did not match expected schema';
84
+ throw new errors_js_1.AIError(message, { kind: 'invalid_response', cause: error });
85
+ }
86
+ }
87
+ function requireString(record, key) {
88
+ const value = record[key];
89
+ if (typeof value !== 'string')
90
+ throw new Error(`Expected ${key} to be a string`);
91
+ return value;
92
+ }
93
+ function requireNumber(record, key) {
94
+ const value = record[key];
95
+ if (typeof value !== 'number' || !Number.isFinite(value))
96
+ throw new Error(`Expected ${key} to be a number`);
97
+ return value;
98
+ }
99
+ function requireStringArray(record, key) {
100
+ const value = record[key];
101
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== 'string')) {
102
+ throw new Error(`Expected ${key} to be a string array`);
103
+ }
104
+ return value;
105
+ }
106
+ function requireOptionalString(record, key) {
107
+ const value = record[key];
108
+ if (value === undefined || value === null)
109
+ return undefined;
110
+ if (typeof value !== 'string')
111
+ throw new Error(`Expected ${key} to be a string when present`);
112
+ return value;
113
+ }
114
+ function tryParse(text) {
115
+ try {
116
+ return { ok: true, value: JSON.parse(text) };
117
+ }
118
+ catch {
119
+ return { ok: false };
120
+ }
121
+ }
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseKeyRef = parseKeyRef;
4
+ exports.envKeySource = envKeySource;
5
+ exports.literalKeySource = literalKeySource;
6
+ exports.memoryKeySource = memoryKeySource;
7
+ exports.chainKeySources = chainKeySources;
8
+ const util_js_1 = require("./util.js");
9
+ function parseKeyRef(value) {
10
+ const trimmed = value?.trim();
11
+ if (!trimmed)
12
+ return { kind: 'none' };
13
+ const braced = /^\$\{([A-Z0-9_]+)\}$/.exec(trimmed);
14
+ if (braced?.[1])
15
+ return { kind: 'env', name: braced[1] };
16
+ const dollar = /^\$([A-Z0-9_]+)$/.exec(trimmed);
17
+ if (dollar?.[1])
18
+ return { kind: 'env', name: dollar[1] };
19
+ if (/^[A-Z][A-Z0-9_]{2,}$/.test(trimmed))
20
+ return { kind: 'env', name: trimmed };
21
+ if (trimmed.startsWith('secret://'))
22
+ return { kind: 'brokered', ref: trimmed };
23
+ if (trimmed.startsWith('stored://'))
24
+ return { kind: 'stored', ref: trimmed.slice('stored://'.length) };
25
+ return { kind: 'literal', value: trimmed };
26
+ }
27
+ function envKeySource(env = (0, util_js_1.globalEnv)()) {
28
+ return {
29
+ async resolve(ref) {
30
+ if (ref.kind === 'none')
31
+ return { ok: true, apiKey: null };
32
+ if (ref.kind !== 'env')
33
+ return { ok: false, reason: 'denied' };
34
+ const apiKey = env[ref.name]?.trim();
35
+ return apiKey ? { ok: true, apiKey } : { ok: false, reason: 'missing' };
36
+ },
37
+ };
38
+ }
39
+ function literalKeySource() {
40
+ return {
41
+ async resolve(ref) {
42
+ if (ref.kind === 'none')
43
+ return { ok: true, apiKey: null };
44
+ if (ref.kind === 'literal')
45
+ return { ok: true, apiKey: ref.value };
46
+ return { ok: false, reason: 'denied' };
47
+ },
48
+ };
49
+ }
50
+ function memoryKeySource(values) {
51
+ return {
52
+ async resolve(ref) {
53
+ if (ref.kind === 'none')
54
+ return { ok: true, apiKey: null };
55
+ if (ref.kind === 'literal')
56
+ return { ok: true, apiKey: ref.value };
57
+ const key = ref.kind === 'env' ? ref.name : ref.ref;
58
+ const value = values[key]?.trim();
59
+ return value ? { ok: true, apiKey: value } : { ok: false, reason: 'missing' };
60
+ },
61
+ };
62
+ }
63
+ function chainKeySources(...sources) {
64
+ return {
65
+ async resolve(ref) {
66
+ let missing = false;
67
+ for (const source of sources) {
68
+ const result = await source.resolve(ref);
69
+ if (result.ok)
70
+ return result;
71
+ if (result.reason === 'locked')
72
+ return result;
73
+ if (result.reason === 'missing') {
74
+ missing = true;
75
+ continue;
76
+ }
77
+ }
78
+ return missing ? { ok: false, reason: 'missing' } : { ok: false, reason: 'denied' };
79
+ },
80
+ };
81
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.allowAllPolicy = allowAllPolicy;
4
+ exports.blocklistPolicy = blocklistPolicy;
5
+ exports.allowlistPolicy = allowlistPolicy;
6
+ exports.composePolicies = composePolicies;
7
+ function allowAllPolicy() {
8
+ return { checkModel: () => ({ allowed: true }) };
9
+ }
10
+ function blocklistPolicy(patterns) {
11
+ return {
12
+ checkModel(provider, model) {
13
+ const target = `${provider}/${model}`;
14
+ const blocked = patterns.find((pattern) => pattern.test(target));
15
+ return blocked ? { allowed: false, reason: `Model blocked by policy: ${blocked}` } : { allowed: true };
16
+ },
17
+ };
18
+ }
19
+ function allowlistPolicy(prefixesByProvider) {
20
+ return {
21
+ checkModel(provider, model) {
22
+ const prefixes = prefixesByProvider[provider];
23
+ if (!prefixes || prefixes.length === 0)
24
+ return { allowed: false, reason: `No models are allowed for provider ${provider}` };
25
+ return prefixes.some((prefix) => prefix === '*' || model.startsWith(prefix))
26
+ ? { allowed: true }
27
+ : { allowed: false, reason: `Model is not allowed for provider ${provider}` };
28
+ },
29
+ };
30
+ }
31
+ function composePolicies(...policies) {
32
+ return {
33
+ checkModel(provider, model) {
34
+ for (const policy of policies) {
35
+ const result = policy.checkModel(provider, model);
36
+ if (!result.allowed)
37
+ return result;
38
+ }
39
+ return { allowed: true };
40
+ },
41
+ };
42
+ }
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ /**
3
+ * Inline reasoning stripper — keeps a reasoning model's chain of thought out of `delta` events
4
+ * and `ChatResult.text`, routing it to the `reasoning` stream event instead.
5
+ *
6
+ * Reasoning models (deepseek-r1, phi4-reasoning, qwq, …) emit their thinking either in the
7
+ * provider's dedicated field — which every engine already maps to `{ type: 'reasoning' }` — or
8
+ * INLINE in the answer as `<think>…</think>`-style blocks. Inline reasoning routinely quotes the
9
+ * system prompt and tool instructions back verbatim, so an app that streams `delta` straight to
10
+ * a screen, a database or a JSON parser leaks it. Every consumer would otherwise write this
11
+ * state machine, and most would get the split-tag and template-opened cases wrong; the handler
12
+ * runs it once, on every engine, unless `HandlerOptions.stripInlineReasoning` is `false`.
13
+ *
14
+ * Ported from AI Server Studio's `services/reasoningFilter.ts` (2026-09-05), which had to exist
15
+ * because the nugget did not do this.
16
+ *
17
+ * Two subtleties the tests pin:
18
+ * - Tags may be split across chunks, so a trailing partial `<thi` is held back until the next
19
+ * chunk decides whether it opens a tag. An unterminated block is reasoning to the end.
20
+ * - The **orphan close**: a closing tag with no opening one at the head of the stream, because
21
+ * the runtime's chat template emitted `<think>` itself. Handled once per stream, only before
22
+ * any visible text has gone out, and only when the closing tag starts its own line — a
23
+ * `</think>` quoted mid-sentence in a real answer is left alone.
24
+ */
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.createReasoningStripper = createReasoningStripper;
27
+ exports.stripReasoningBlocksDetailed = stripReasoningBlocksDetailed;
28
+ exports.stripReasoningBlocks = stripReasoningBlocks;
29
+ exports.containsReasoningBlock = containsReasoningBlock;
30
+ const TAG_PAIRS = [
31
+ { open: '<think>', close: '</think>' },
32
+ { open: '<thinking>', close: '</thinking>' },
33
+ { open: '<reasoning>', close: '</reasoning>' },
34
+ { open: '<|begin_of_thought|>', close: '<|end_of_thought|>' },
35
+ ];
36
+ const LONGEST_TAG = Math.max(...TAG_PAIRS.flatMap((p) => [p.open.length, p.close.length]));
37
+ function partialTagSuffixLength(text) {
38
+ const lower = text.toLowerCase();
39
+ const maxLen = Math.min(LONGEST_TAG - 1, lower.length);
40
+ for (let len = maxLen; len >= 1; len -= 1) {
41
+ const tail = lower.slice(lower.length - len);
42
+ if (tail[0] !== '<')
43
+ continue;
44
+ for (const pair of TAG_PAIRS) {
45
+ if (pair.open.startsWith(tail) || pair.close.startsWith(tail))
46
+ return len;
47
+ }
48
+ }
49
+ return 0;
50
+ }
51
+ function closesOwnLine(text, idx) {
52
+ let i = idx - 1;
53
+ while (i >= 0 && (text[i] === ' ' || text[i] === '\t' || text[i] === '\r'))
54
+ i -= 1;
55
+ return i < 0 || text[i] === '\n';
56
+ }
57
+ function createReasoningStripper() {
58
+ let pending = '';
59
+ let inReasoning = false;
60
+ let closeTag = '';
61
+ let emittedVisible = false;
62
+ let orphanConsumed = false;
63
+ function process(input, flushAll) {
64
+ let text = pending + input;
65
+ pending = '';
66
+ let visible = '';
67
+ let reasoning = '';
68
+ while (text.length > 0) {
69
+ const lower = text.toLowerCase();
70
+ if (inReasoning) {
71
+ const idx = lower.indexOf(closeTag);
72
+ if (idx === -1) {
73
+ const hold = flushAll ? 0 : partialTagSuffixLength(text);
74
+ const consumed = text.length - hold;
75
+ reasoning += text.slice(0, consumed);
76
+ pending = text.slice(consumed);
77
+ text = '';
78
+ if (flushAll) {
79
+ reasoning += pending;
80
+ pending = '';
81
+ }
82
+ break;
83
+ }
84
+ reasoning += text.slice(0, idx);
85
+ text = text.slice(idx + closeTag.length);
86
+ inReasoning = false;
87
+ closeTag = '';
88
+ if (text.startsWith('\n'))
89
+ text = text.slice(1);
90
+ continue;
91
+ }
92
+ let openIdx = -1;
93
+ let pair = null;
94
+ for (const p of TAG_PAIRS) {
95
+ const i = lower.indexOf(p.open);
96
+ if (i !== -1 && (openIdx === -1 || i < openIdx)) {
97
+ openIdx = i;
98
+ pair = p;
99
+ }
100
+ }
101
+ let orphanCloseIdx = -1;
102
+ let orphanCloseLen = 0;
103
+ if (!emittedVisible && !orphanConsumed) {
104
+ for (const p of TAG_PAIRS) {
105
+ const i = lower.indexOf(p.close);
106
+ if (i !== -1 && (orphanCloseIdx === -1 || i < orphanCloseIdx) && closesOwnLine(text, i)) {
107
+ orphanCloseIdx = i;
108
+ orphanCloseLen = p.close.length;
109
+ }
110
+ }
111
+ }
112
+ if (orphanCloseIdx !== -1 && (openIdx === -1 || orphanCloseIdx < openIdx)) {
113
+ orphanConsumed = true;
114
+ reasoning += text.slice(0, orphanCloseIdx);
115
+ text = text.slice(orphanCloseIdx + orphanCloseLen).replace(/^\n+/, '');
116
+ continue;
117
+ }
118
+ if (openIdx === -1 || !pair) {
119
+ const hold = flushAll ? 0 : partialTagSuffixLength(text);
120
+ visible += text.slice(0, text.length - hold);
121
+ pending = text.slice(text.length - hold);
122
+ text = '';
123
+ break;
124
+ }
125
+ visible += text.slice(0, openIdx);
126
+ text = text.slice(openIdx + pair.open.length);
127
+ inReasoning = true;
128
+ closeTag = pair.close;
129
+ }
130
+ if (visible.trim().length > 0)
131
+ emittedVisible = true;
132
+ return { visible, reasoning };
133
+ }
134
+ return {
135
+ push: (chunk) => process(chunk, false),
136
+ end: () => process('', true),
137
+ get inReasoning() {
138
+ return inReasoning;
139
+ },
140
+ };
141
+ }
142
+ /** Whole-string form: `{ visible, reasoning }` for already-buffered text. */
143
+ function stripReasoningBlocksDetailed(text) {
144
+ if (!text)
145
+ return { visible: '', reasoning: '' };
146
+ const stripper = createReasoningStripper();
147
+ const a = stripper.push(text);
148
+ const b = stripper.end();
149
+ return { visible: (a.visible + b.visible).trim(), reasoning: a.reasoning + b.reasoning };
150
+ }
151
+ /** Whole-string form returning only the visible text. */
152
+ function stripReasoningBlocks(text) {
153
+ return stripReasoningBlocksDetailed(text).visible;
154
+ }
155
+ /** Cheap pre-check: does `text` carry any inline reasoning tag (open or close)? */
156
+ function containsReasoningBlock(text) {
157
+ const lower = text.toLowerCase();
158
+ return TAG_PAIRS.some((p) => lower.includes(p.open) || lower.includes(p.close));
159
+ }
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SessionRedactor = exports.LABELED_SECRET_PATTERNS = exports.SECRET_PATTERNS = void 0;
4
+ exports.createDefaultRedactor = createDefaultRedactor;
5
+ /**
6
+ * Prefixed secret formats. Each pattern is anchored on a vendor prefix that is
7
+ * distinctive enough that a match is a secret, not a coincidence.
8
+ */
9
+ exports.SECRET_PATTERNS = [
10
+ /sk-[A-Za-z0-9_-]{20,}/g,
11
+ /sk-proj-[A-Za-z0-9_-]{20,}/g,
12
+ /sk-ant-[A-Za-z0-9_-]{20,}/g,
13
+ /AIza[0-9A-Za-z_-]{20,}/g,
14
+ /gsk_[0-9A-Za-z_-]{20,}/g,
15
+ /xai-[0-9A-Za-z_-]{20,}/g,
16
+ /hf_[0-9A-Za-z]{20,}/g,
17
+ /nvapi-[0-9A-Za-z_-]{20,}/g,
18
+ /ghp_[0-9A-Za-z_]{20,}/g,
19
+ /github_pat_[0-9A-Za-z_]{20,}/g,
20
+ /glpat-[0-9A-Za-z_-]{20,}/g,
21
+ /\bBearer\s+[A-Za-z0-9._~+/=-]{20,}/gi,
22
+ /xox[baprs]-[0-9A-Za-z-]{10,}/g,
23
+ /AKIA[0-9A-Z]{16}/g,
24
+ /ASIA[0-9A-Z]{16}/g,
25
+ /SG\.[0-9A-Za-z_-]{10,}\.[0-9A-Za-z_-]{10,}/g,
26
+ /rk_live_[0-9A-Za-z]{20,}/g,
27
+ /pk_live_[0-9A-Za-z]{20,}/g,
28
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
29
+ /eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g,
30
+ ];
31
+ /**
32
+ * Unprefixed/opaque secrets — Azure OpenAI `api-key` values, AWS *secret*
33
+ * access keys, generic bearer/session tokens — carry no distinctive prefix, so
34
+ * matching them on shape alone would redact commit SHAs, content hashes, and
35
+ * base64 payloads. These patterns instead anchor on the *label* that precedes
36
+ * the value (`api-key: …`, `"aws_secret_access_key": "…"`, `password=…`) and
37
+ * redact only the value, keeping the label readable in logs.
38
+ *
39
+ * Exact-match registration (`SessionRedactor.addSecret`, which the handler does
40
+ * for every resolved key) remains the only guaranteed catch for a bare,
41
+ * unlabeled, unprefixed secret.
42
+ */
43
+ exports.LABELED_SECRET_PATTERNS = [
44
+ new RegExp(
45
+ // Lookbehind rather than `\b` so an underscore-joined prefix still matches
46
+ // the label (`aws_secret_access_key`, `azure_api_key`).
47
+ String.raw `(?<![A-Za-z0-9])(api[_-]?key|apikey|secret[_-]?access[_-]?key|access[_-]?token|auth[_-]?token|session[_-]?token|client[_-]?secret|refresh[_-]?token|private[_-]?key|password|passwd)` +
48
+ // Optional closing quote handles JSON keys (`"api_key": "…"`).
49
+ String.raw `(["']?\s*[:=]\s*|\s+)` +
50
+ String.raw `(["']?)([A-Za-z0-9/+_.~-]{16,}={0,2})\3`, 'gi'),
51
+ ];
52
+ function createDefaultRedactor(extraSecrets = []) {
53
+ const secrets = new Set([...extraSecrets].filter((value) => value.length >= 6));
54
+ return {
55
+ redact(text) {
56
+ let output = text;
57
+ for (const pattern of exports.SECRET_PATTERNS)
58
+ output = output.replace(pattern, '[REDACTED]');
59
+ for (const pattern of exports.LABELED_SECRET_PATTERNS) {
60
+ output = output.replace(pattern, (_match, label, separator, quote) => `${label}${separator}${quote}[REDACTED]${quote}`);
61
+ }
62
+ for (const secret of secrets)
63
+ output = output.split(secret).join('[REDACTED]');
64
+ return output;
65
+ },
66
+ };
67
+ }
68
+ class SessionRedactor {
69
+ secrets = new Set();
70
+ addSecret(value) {
71
+ if (value && value.length >= 6)
72
+ this.secrets.add(value);
73
+ }
74
+ redact(text) {
75
+ return createDefaultRedactor(this.secrets).redact(text);
76
+ }
77
+ }
78
+ exports.SessionRedactor = SessionRedactor;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.estimateTokens = estimateTokens;
4
+ exports.estimatedUsage = estimatedUsage;
5
+ exports.mergeUsage = mergeUsage;
6
+ function estimateTokens(text) {
7
+ return Math.ceil(text.length / 4);
8
+ }
9
+ function estimatedUsage(inputText, outputText) {
10
+ return {
11
+ inputTokens: estimateTokens(inputText),
12
+ outputTokens: estimateTokens(outputText),
13
+ estimated: true,
14
+ };
15
+ }
16
+ function mergeUsage(a, b) {
17
+ return {
18
+ inputTokens: (a.inputTokens ?? 0) + (b.inputTokens ?? 0),
19
+ outputTokens: (a.outputTokens ?? 0) + (b.outputTokens ?? 0),
20
+ estimated: a.estimated || b.estimated,
21
+ };
22
+ }
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withTimeout = withTimeout;
4
+ exports.fetchJson = fetchJson;
5
+ exports.postResponse = postResponse;
6
+ exports.sseLines = sseLines;
7
+ exports.ndjsonLines = ndjsonLines;
8
+ exports.textLines = textLines;
9
+ const errors_js_1 = require("./errors.js");
10
+ function withTimeout(ms, external, idleMs) {
11
+ const controller = new AbortController();
12
+ let didTimeOut = false;
13
+ const timer = setTimeout(() => {
14
+ didTimeOut = true;
15
+ controller.abort(new errors_js_1.AIError(`Request timed out after ${ms}ms`, { kind: 'timeout' }));
16
+ }, ms);
17
+ // Optional idle timeout: aborts only when no chunk has arrived for `idleMs`,
18
+ // rearmed on every `bump()`. Keeps a long but healthy stream alive while the
19
+ // total `ms` ceiling still bounds a truly stuck call.
20
+ let idleTimer;
21
+ const armIdle = () => {
22
+ if (idleMs === undefined)
23
+ return;
24
+ if (idleTimer)
25
+ clearTimeout(idleTimer);
26
+ idleTimer = setTimeout(() => {
27
+ didTimeOut = true;
28
+ controller.abort(new errors_js_1.AIError(`Request idle for ${idleMs}ms`, { kind: 'timeout' }));
29
+ }, idleMs);
30
+ };
31
+ const onAbort = () => controller.abort(external?.reason);
32
+ if (external) {
33
+ if (external.aborted)
34
+ onAbort();
35
+ else
36
+ external.addEventListener('abort', onAbort, { once: true });
37
+ }
38
+ armIdle();
39
+ return {
40
+ signal: controller.signal,
41
+ done() {
42
+ clearTimeout(timer);
43
+ if (idleTimer)
44
+ clearTimeout(idleTimer);
45
+ external?.removeEventListener('abort', onAbort);
46
+ },
47
+ timedOut() {
48
+ return didTimeOut;
49
+ },
50
+ bump() {
51
+ armIdle();
52
+ },
53
+ };
54
+ }
55
+ async function fetchJson(url, init) {
56
+ const timeout = withTimeout(init.timeoutMs, init.signal ?? undefined);
57
+ const started = Date.now();
58
+ try {
59
+ const res = await fetch(url, { ...init, signal: timeout.signal });
60
+ const raw = await res.text().catch(() => '');
61
+ if (!res.ok)
62
+ throw (0, errors_js_1.classify)(res.status, raw, init.provider, res.headers);
63
+ return { data: raw.trim() ? tolerantJson(raw) : null, status: res.status, totalMs: Date.now() - started };
64
+ }
65
+ catch (error) {
66
+ if (timeout.timedOut())
67
+ throw new errors_js_1.AIError(`Request timed out after ${init.timeoutMs}ms`, { kind: 'timeout', provider: init.provider });
68
+ throw (0, errors_js_1.fromUnknown)(error, init.provider);
69
+ }
70
+ finally {
71
+ timeout.done();
72
+ }
73
+ }
74
+ /**
75
+ * POST a JSON body and return the raw {@link Response} for streaming.
76
+ *
77
+ * The caller owns the abort/timeout `signal` and is responsible for keeping it
78
+ * alive until the response body has been fully consumed — passing a
79
+ * `withTimeout()` signal here and cleaning it up only after the stream ends is
80
+ * what lets external cancellation and the timeout apply for the *whole* stream,
81
+ * not merely the connection handshake. Non-2xx responses are classified and
82
+ * thrown before the body is handed back.
83
+ */
84
+ async function postResponse(url, body, headers, signal, provider) {
85
+ let res;
86
+ try {
87
+ res = await fetch(url, {
88
+ method: 'POST',
89
+ headers: { 'content-type': 'application/json', ...headers },
90
+ body: JSON.stringify(body),
91
+ signal,
92
+ });
93
+ }
94
+ catch (error) {
95
+ throw (0, errors_js_1.fromUnknown)(error, provider);
96
+ }
97
+ if (!res.ok) {
98
+ const raw = await res.text().catch(() => '');
99
+ throw (0, errors_js_1.classify)(res.status, raw, provider, res.headers);
100
+ }
101
+ return res;
102
+ }
103
+ async function* sseLines(res) {
104
+ for await (const line of textLines(res)) {
105
+ const trimmed = line.trim();
106
+ if (!trimmed || trimmed.startsWith(':'))
107
+ continue;
108
+ if (!trimmed.startsWith('data:'))
109
+ continue;
110
+ const data = trimmed.slice(5).trim();
111
+ if (!data || data === '[DONE]')
112
+ continue;
113
+ yield data;
114
+ }
115
+ }
116
+ async function* ndjsonLines(res) {
117
+ for await (const line of textLines(res)) {
118
+ const trimmed = line.trim();
119
+ if (!trimmed)
120
+ continue;
121
+ try {
122
+ yield JSON.parse(trimmed);
123
+ }
124
+ catch {
125
+ throw new errors_js_1.AIError('Provider returned malformed NDJSON', { kind: 'invalid_response' });
126
+ }
127
+ }
128
+ }
129
+ async function* textLines(res) {
130
+ if (!res.body) {
131
+ const raw = await res.text().catch(() => '');
132
+ for (const line of raw.split(/\r?\n/))
133
+ yield line;
134
+ return;
135
+ }
136
+ const reader = res.body.getReader();
137
+ const decoder = new TextDecoder();
138
+ let buffer = '';
139
+ let completed = false;
140
+ try {
141
+ for (;;) {
142
+ const { value, done } = await reader.read();
143
+ if (done) {
144
+ completed = true;
145
+ break;
146
+ }
147
+ buffer += decoder.decode(value, { stream: true });
148
+ let newlineIndex;
149
+ while ((newlineIndex = buffer.indexOf('\n')) >= 0) {
150
+ const line = buffer.slice(0, newlineIndex);
151
+ buffer = buffer.slice(newlineIndex + 1);
152
+ yield line;
153
+ }
154
+ }
155
+ buffer += decoder.decode();
156
+ if (buffer.trim())
157
+ yield buffer;
158
+ }
159
+ finally {
160
+ if (!completed)
161
+ await reader.cancel().catch(() => undefined);
162
+ reader.releaseLock();
163
+ }
164
+ }
165
+ function tolerantJson(raw) {
166
+ try {
167
+ return JSON.parse(raw);
168
+ }
169
+ catch {
170
+ return { text: raw };
171
+ }
172
+ }
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.modelRef = modelRef;
4
+ function modelRef(source, model) {
5
+ return `${source.provider}/${model}`;
6
+ }