@jxburros/ai-nugget 0.3.1 → 0.6.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 (183) hide show
  1. package/README.md +134 -271
  2. package/UPGRADING.md +158 -0
  3. package/dist/adapters/engines/anthropic.d.ts +7 -1
  4. package/dist/adapters/engines/anthropic.js +64 -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 +17 -1
  8. package/dist/adapters/engines/google.js +93 -29
  9. package/dist/adapters/engines/ollama.d.ts +2 -1
  10. package/dist/adapters/engines/ollama.js +51 -15
  11. package/dist/adapters/engines/openaiChat.d.ts +2 -1
  12. package/dist/adapters/engines/openaiChat.js +60 -30
  13. package/dist/adapters/profiles.d.ts +21 -1
  14. package/dist/adapters/profiles.js +12 -2
  15. package/dist/agent/loop.d.ts +43 -1
  16. package/dist/agent/loop.js +120 -13
  17. package/dist/cjs/adapters/engines/anthropic.js +308 -0
  18. package/dist/cjs/adapters/engines/base.js +147 -0
  19. package/dist/cjs/adapters/engines/google.js +284 -0
  20. package/dist/cjs/adapters/engines/ollama.js +199 -0
  21. package/dist/cjs/adapters/engines/openaiChat.js +261 -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 +372 -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 +561 -0
  30. package/dist/cjs/index.js +27 -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/redact.js +78 -0
  36. package/dist/cjs/tokens.js +22 -0
  37. package/dist/cjs/transport.js +172 -0
  38. package/dist/cjs/types.js +6 -0
  39. package/dist/cjs/util.js +112 -0
  40. package/dist/connect.d.ts +31 -0
  41. package/dist/connect.js +25 -0
  42. package/dist/errors.js +25 -1
  43. package/dist/handler.d.ts +61 -1
  44. package/dist/handler.js +247 -61
  45. package/dist/index.d.ts +1 -0
  46. package/dist/index.js +1 -0
  47. package/dist/keys.js +1 -4
  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 +96 -2
  53. package/dist/util.d.ts +16 -0
  54. package/dist/util.js +50 -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 +305 -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 +281 -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 +196 -0
  71. package/nugget/dist/adapters/engines/ollama.js.map +1 -0
  72. package/nugget/dist/adapters/engines/openaiChat.d.ts +16 -0
  73. package/nugget/dist/adapters/engines/openaiChat.d.ts.map +1 -0
  74. package/nugget/dist/adapters/engines/openaiChat.js +258 -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 +112 -0
  89. package/nugget/dist/agent/loop.d.ts.map +1 -0
  90. package/nugget/dist/agent/loop.js +370 -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 +308 -0
  97. package/nugget/dist/cjs/adapters/engines/base.js +147 -0
  98. package/nugget/dist/cjs/adapters/engines/google.js +284 -0
  99. package/nugget/dist/cjs/adapters/engines/ollama.js +199 -0
  100. package/nugget/dist/cjs/adapters/engines/openaiChat.js +261 -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 +372 -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 +561 -0
  109. package/nugget/dist/cjs/index.js +27 -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/redact.js +78 -0
  115. package/nugget/dist/cjs/tokens.js +22 -0
  116. package/nugget/dist/cjs/transport.js +172 -0
  117. package/nugget/dist/cjs/types.js +6 -0
  118. package/nugget/dist/cjs/util.js +112 -0
  119. package/nugget/dist/connect.d.ts +31 -0
  120. package/nugget/dist/connect.d.ts.map +1 -0
  121. package/nugget/dist/connect.js +25 -0
  122. package/nugget/dist/connect.js.map +1 -0
  123. package/nugget/dist/errors.d.ts +22 -0
  124. package/nugget/dist/errors.d.ts.map +1 -0
  125. package/nugget/dist/errors.js +104 -0
  126. package/nugget/dist/errors.js.map +1 -0
  127. package/nugget/dist/handler.d.ts +108 -0
  128. package/nugget/dist/handler.d.ts.map +1 -0
  129. package/nugget/dist/handler.js +557 -0
  130. package/nugget/dist/handler.js.map +1 -0
  131. package/nugget/dist/index.d.ts +12 -0
  132. package/nugget/dist/index.d.ts.map +1 -0
  133. package/nugget/dist/index.js +12 -0
  134. package/nugget/dist/index.js.map +1 -0
  135. package/nugget/dist/json.d.ts +7 -0
  136. package/nugget/dist/json.d.ts.map +1 -0
  137. package/nugget/dist/json.js +114 -0
  138. package/nugget/dist/json.js.map +1 -0
  139. package/nugget/dist/keys.d.ts +7 -0
  140. package/nugget/dist/keys.d.ts.map +1 -0
  141. package/nugget/dist/keys.js +75 -0
  142. package/nugget/dist/keys.js.map +1 -0
  143. package/nugget/dist/policy.d.ts +6 -0
  144. package/nugget/dist/policy.d.ts.map +1 -0
  145. package/nugget/dist/policy.js +37 -0
  146. package/nugget/dist/policy.js.map +1 -0
  147. package/nugget/dist/redact.d.ts +26 -0
  148. package/nugget/dist/redact.d.ts.map +1 -0
  149. package/nugget/dist/redact.js +74 -0
  150. package/nugget/dist/redact.js.map +1 -0
  151. package/nugget/dist/tokens.d.ts +5 -0
  152. package/nugget/dist/tokens.d.ts.map +1 -0
  153. package/nugget/dist/tokens.js +18 -0
  154. package/nugget/dist/tokens.js.map +1 -0
  155. package/nugget/dist/transport.d.ts +30 -0
  156. package/nugget/dist/transport.d.ts.map +1 -0
  157. package/nugget/dist/transport.js +165 -0
  158. package/nugget/dist/transport.js.map +1 -0
  159. package/nugget/dist/types.d.ts +285 -0
  160. package/nugget/dist/types.d.ts.map +1 -0
  161. package/nugget/dist/types.js +4 -0
  162. package/nugget/dist/types.js.map +1 -0
  163. package/nugget/dist/util.d.ts +24 -0
  164. package/nugget/dist/util.d.ts.map +1 -0
  165. package/nugget/dist/util.js +101 -0
  166. package/nugget/dist/util.js.map +1 -0
  167. package/nugget/src/adapters/engines/anthropic.ts +62 -37
  168. package/nugget/src/adapters/engines/base.ts +50 -7
  169. package/nugget/src/adapters/engines/google.ts +92 -29
  170. package/nugget/src/adapters/engines/ollama.ts +51 -17
  171. package/nugget/src/adapters/engines/openaiChat.ts +61 -32
  172. package/nugget/src/adapters/profiles.ts +53 -3
  173. package/nugget/src/agent/loop.ts +158 -14
  174. package/nugget/src/connect.ts +48 -0
  175. package/nugget/src/errors.ts +25 -1
  176. package/nugget/src/handler.ts +286 -59
  177. package/nugget/src/index.ts +1 -0
  178. package/nugget/src/keys.ts +1 -5
  179. package/nugget/src/redact.ts +32 -1
  180. package/nugget/src/transport.ts +20 -1
  181. package/nugget/src/types.ts +96 -1
  182. package/nugget/src/util.ts +55 -0
  183. package/package.json +20 -6
@@ -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,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
+ }
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.asRecord = asRecord;
4
+ exports.asString = asString;
5
+ exports.asNumber = asNumber;
6
+ exports.promptChars = promptChars;
7
+ exports.textFromMessages = textFromMessages;
8
+ exports.globalEnv = globalEnv;
9
+ exports.applyProviderOptions = applyProviderOptions;
10
+ exports.joinUrl = joinUrl;
11
+ exports.mapWithConcurrency = mapWithConcurrency;
12
+ exports.sleep = sleep;
13
+ function asRecord(value) {
14
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
15
+ }
16
+ function asString(value) {
17
+ return typeof value === 'string' ? value : undefined;
18
+ }
19
+ function asNumber(value) {
20
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
21
+ }
22
+ function promptChars(messages) {
23
+ return messages.reduce((sum, message) => {
24
+ if (typeof message.content === 'string')
25
+ return sum + message.content.length;
26
+ return sum + message.content.reduce((partSum, part) => partSum + (part.text?.length ?? 0) + (part.imageBase64?.length ?? 0), 0);
27
+ }, 0);
28
+ }
29
+ function textFromMessages(messages) {
30
+ return messages.map((message) => {
31
+ if (typeof message.content === 'string')
32
+ return message.content;
33
+ return message.content.map((part) => part.text ?? '').join('');
34
+ }).join('\n');
35
+ }
36
+ function globalEnv() {
37
+ const maybeProcess = globalThis;
38
+ return maybeProcess.process?.env ?? {};
39
+ }
40
+ /**
41
+ * Merges app-supplied {@link ChatRequest.providerOptions} into a provider
42
+ * request body: shallow at the top level, and one level deep for each key in
43
+ * `nestedKeys` where both sides are plain objects (Ollama `options`, Google
44
+ * `generationConfig`). Values in `providerOptions` win on collision.
45
+ */
46
+ function applyProviderOptions(body, providerOptions, nestedKeys = []) {
47
+ if (!providerOptions)
48
+ return body;
49
+ const merged = { ...body };
50
+ for (const [key, value] of Object.entries(providerOptions)) {
51
+ const base = asRecord(merged[key]);
52
+ if (nestedKeys.includes(key) && base && asRecord(value)) {
53
+ merged[key] = { ...base, ...value };
54
+ }
55
+ else {
56
+ merged[key] = value;
57
+ }
58
+ }
59
+ return merged;
60
+ }
61
+ /** Joins a base URL and path without producing a double slash, even if `base` ends with `/`. */
62
+ function joinUrl(base, path) {
63
+ return `${base.replace(/\/+$/, '')}/${path.replace(/^\/+/, '')}`;
64
+ }
65
+ /**
66
+ * Runs `fn` over `items` with at most `limit` in flight at once, preserving
67
+ * input order in the returned array. Used to probe many local models
68
+ * concurrently without opening an unbounded number of sockets.
69
+ */
70
+ async function mapWithConcurrency(items, limit, fn) {
71
+ const results = new Array(items.length);
72
+ let next = 0;
73
+ const workerCount = Math.max(1, Math.min(limit, items.length));
74
+ const worker = async () => {
75
+ for (;;) {
76
+ const index = next;
77
+ next += 1;
78
+ if (index >= items.length)
79
+ return;
80
+ results[index] = await fn(items[index], index);
81
+ }
82
+ };
83
+ await Promise.all(Array.from({ length: workerCount }, worker));
84
+ return results;
85
+ }
86
+ function sleep(ms, signal) {
87
+ if (ms <= 0)
88
+ return Promise.resolve();
89
+ return new Promise((resolve, reject) => {
90
+ let settled = false;
91
+ const cleanup = () => signal?.removeEventListener('abort', onAbort);
92
+ const timer = setTimeout(() => {
93
+ settled = true;
94
+ cleanup();
95
+ resolve();
96
+ }, ms);
97
+ const onAbort = () => {
98
+ if (settled)
99
+ return;
100
+ settled = true;
101
+ clearTimeout(timer);
102
+ cleanup();
103
+ reject(new DOMException('Sleep aborted', 'AbortError'));
104
+ };
105
+ if (signal) {
106
+ if (signal.aborted)
107
+ onAbort();
108
+ else
109
+ signal.addEventListener('abort', onAbort, { once: true });
110
+ }
111
+ });
112
+ }
@@ -0,0 +1,31 @@
1
+ import type { Connection } from './types.js';
2
+ export interface EnvConnectionOptions {
3
+ /** Read from this env-like object instead of the process/global environment. */
4
+ env?: Record<string, string | undefined>;
5
+ /** Connection id. Defaults to `'app'`. */
6
+ id?: string;
7
+ /** Env var names to read (override if an app already uses different names). */
8
+ providerVar?: string;
9
+ modelVar?: string;
10
+ keyEnvVar?: string;
11
+ baseUrlVar?: string;
12
+ /** Fallbacks used when the corresponding env var is unset. */
13
+ defaultProvider?: string;
14
+ defaultModel?: string;
15
+ defaultKeyEnv?: string;
16
+ }
17
+ export interface EnvConnection {
18
+ connection: Connection;
19
+ model: string;
20
+ }
21
+ /**
22
+ * Resolves a server-side `Connection` plus a default `model` from app-owned
23
+ * env vars (`AI_PROVIDER`, `AI_MODEL`, `AI_KEY_ENV`, `AI_BASE_URL` unless
24
+ * overridden). This only collapses the config-reading boilerplate that was
25
+ * being hand-rolled the same way in every small server app; it does not add
26
+ * policy. `provider`/`baseUrl` still come from the server's own environment,
27
+ * never from client input, and the returned `keyRef` still resolves through
28
+ * `KeySource` at call time like any other `Connection`.
29
+ */
30
+ export declare function envConnection(options?: EnvConnectionOptions): EnvConnection;
31
+ //# sourceMappingURL=connect.d.ts.map