@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.
- package/README.md +134 -271
- package/UPGRADING.md +158 -0
- package/dist/adapters/engines/anthropic.d.ts +7 -1
- package/dist/adapters/engines/anthropic.js +64 -37
- package/dist/adapters/engines/base.d.ts +29 -2
- package/dist/adapters/engines/base.js +52 -8
- package/dist/adapters/engines/google.d.ts +17 -1
- package/dist/adapters/engines/google.js +93 -29
- package/dist/adapters/engines/ollama.d.ts +2 -1
- package/dist/adapters/engines/ollama.js +51 -15
- package/dist/adapters/engines/openaiChat.d.ts +2 -1
- package/dist/adapters/engines/openaiChat.js +60 -30
- package/dist/adapters/profiles.d.ts +21 -1
- package/dist/adapters/profiles.js +12 -2
- package/dist/agent/loop.d.ts +43 -1
- package/dist/agent/loop.js +120 -13
- package/dist/cjs/adapters/engines/anthropic.js +308 -0
- package/dist/cjs/adapters/engines/base.js +147 -0
- package/dist/cjs/adapters/engines/google.js +284 -0
- package/dist/cjs/adapters/engines/ollama.js +199 -0
- package/dist/cjs/adapters/engines/openaiChat.js +261 -0
- package/dist/cjs/adapters/index.js +22 -0
- package/dist/cjs/adapters/profiles.js +125 -0
- package/dist/cjs/agent/index.js +18 -0
- package/dist/cjs/agent/loop.js +372 -0
- package/dist/cjs/agent/tools.js +51 -0
- package/dist/cjs/connect.js +27 -0
- package/dist/cjs/errors.js +110 -0
- package/dist/cjs/handler.js +561 -0
- package/dist/cjs/index.js +27 -0
- package/dist/cjs/json.js +121 -0
- package/dist/cjs/keys.js +81 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/policy.js +42 -0
- package/dist/cjs/redact.js +78 -0
- package/dist/cjs/tokens.js +22 -0
- package/dist/cjs/transport.js +172 -0
- package/dist/cjs/types.js +6 -0
- package/dist/cjs/util.js +112 -0
- package/dist/connect.d.ts +31 -0
- package/dist/connect.js +25 -0
- package/dist/errors.js +25 -1
- package/dist/handler.d.ts +61 -1
- package/dist/handler.js +247 -61
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/keys.js +1 -4
- package/dist/redact.d.ts +18 -0
- package/dist/redact.js +29 -1
- package/dist/transport.d.ts +3 -1
- package/dist/transport.js +21 -1
- package/dist/types.d.ts +96 -2
- package/dist/util.d.ts +16 -0
- package/dist/util.js +50 -0
- package/nugget/VERSION.txt +2 -2
- package/nugget/dist/adapters/engines/anthropic.d.ts +14 -0
- package/nugget/dist/adapters/engines/anthropic.d.ts.map +1 -0
- package/nugget/dist/adapters/engines/anthropic.js +305 -0
- package/nugget/dist/adapters/engines/anthropic.js.map +1 -0
- package/nugget/dist/adapters/engines/base.d.ts +50 -0
- package/nugget/dist/adapters/engines/base.d.ts.map +1 -0
- package/nugget/dist/adapters/engines/base.js +137 -0
- package/nugget/dist/adapters/engines/base.js.map +1 -0
- package/nugget/dist/adapters/engines/google.d.ts +24 -0
- package/nugget/dist/adapters/engines/google.d.ts.map +1 -0
- package/nugget/dist/adapters/engines/google.js +281 -0
- package/nugget/dist/adapters/engines/google.js.map +1 -0
- package/nugget/dist/adapters/engines/ollama.d.ts +11 -0
- package/nugget/dist/adapters/engines/ollama.d.ts.map +1 -0
- package/nugget/dist/adapters/engines/ollama.js +196 -0
- package/nugget/dist/adapters/engines/ollama.js.map +1 -0
- package/nugget/dist/adapters/engines/openaiChat.d.ts +16 -0
- package/nugget/dist/adapters/engines/openaiChat.d.ts.map +1 -0
- package/nugget/dist/adapters/engines/openaiChat.js +258 -0
- package/nugget/dist/adapters/engines/openaiChat.js.map +1 -0
- package/nugget/dist/adapters/index.d.ts +5 -0
- package/nugget/dist/adapters/index.d.ts.map +1 -0
- package/nugget/dist/adapters/index.js +17 -0
- package/nugget/dist/adapters/index.js.map +1 -0
- package/nugget/dist/adapters/profiles.d.ts +62 -0
- package/nugget/dist/adapters/profiles.d.ts.map +1 -0
- package/nugget/dist/adapters/profiles.js +121 -0
- package/nugget/dist/adapters/profiles.js.map +1 -0
- package/nugget/dist/agent/index.d.ts +3 -0
- package/nugget/dist/agent/index.d.ts.map +1 -0
- package/nugget/dist/agent/index.js +3 -0
- package/nugget/dist/agent/index.js.map +1 -0
- package/nugget/dist/agent/loop.d.ts +112 -0
- package/nugget/dist/agent/loop.d.ts.map +1 -0
- package/nugget/dist/agent/loop.js +370 -0
- package/nugget/dist/agent/loop.js.map +1 -0
- package/nugget/dist/agent/tools.d.ts +29 -0
- package/nugget/dist/agent/tools.d.ts.map +1 -0
- package/nugget/dist/agent/tools.js +48 -0
- package/nugget/dist/agent/tools.js.map +1 -0
- package/nugget/dist/cjs/adapters/engines/anthropic.js +308 -0
- package/nugget/dist/cjs/adapters/engines/base.js +147 -0
- package/nugget/dist/cjs/adapters/engines/google.js +284 -0
- package/nugget/dist/cjs/adapters/engines/ollama.js +199 -0
- package/nugget/dist/cjs/adapters/engines/openaiChat.js +261 -0
- package/nugget/dist/cjs/adapters/index.js +22 -0
- package/nugget/dist/cjs/adapters/profiles.js +125 -0
- package/nugget/dist/cjs/agent/index.js +18 -0
- package/nugget/dist/cjs/agent/loop.js +372 -0
- package/nugget/dist/cjs/agent/tools.js +51 -0
- package/nugget/dist/cjs/connect.js +27 -0
- package/nugget/dist/cjs/errors.js +110 -0
- package/nugget/dist/cjs/handler.js +561 -0
- package/nugget/dist/cjs/index.js +27 -0
- package/nugget/dist/cjs/json.js +121 -0
- package/nugget/dist/cjs/keys.js +81 -0
- package/nugget/dist/cjs/package.json +3 -0
- package/nugget/dist/cjs/policy.js +42 -0
- package/nugget/dist/cjs/redact.js +78 -0
- package/nugget/dist/cjs/tokens.js +22 -0
- package/nugget/dist/cjs/transport.js +172 -0
- package/nugget/dist/cjs/types.js +6 -0
- package/nugget/dist/cjs/util.js +112 -0
- package/nugget/dist/connect.d.ts +31 -0
- package/nugget/dist/connect.d.ts.map +1 -0
- package/nugget/dist/connect.js +25 -0
- package/nugget/dist/connect.js.map +1 -0
- package/nugget/dist/errors.d.ts +22 -0
- package/nugget/dist/errors.d.ts.map +1 -0
- package/nugget/dist/errors.js +104 -0
- package/nugget/dist/errors.js.map +1 -0
- package/nugget/dist/handler.d.ts +108 -0
- package/nugget/dist/handler.d.ts.map +1 -0
- package/nugget/dist/handler.js +557 -0
- package/nugget/dist/handler.js.map +1 -0
- package/nugget/dist/index.d.ts +12 -0
- package/nugget/dist/index.d.ts.map +1 -0
- package/nugget/dist/index.js +12 -0
- package/nugget/dist/index.js.map +1 -0
- package/nugget/dist/json.d.ts +7 -0
- package/nugget/dist/json.d.ts.map +1 -0
- package/nugget/dist/json.js +114 -0
- package/nugget/dist/json.js.map +1 -0
- package/nugget/dist/keys.d.ts +7 -0
- package/nugget/dist/keys.d.ts.map +1 -0
- package/nugget/dist/keys.js +75 -0
- package/nugget/dist/keys.js.map +1 -0
- package/nugget/dist/policy.d.ts +6 -0
- package/nugget/dist/policy.d.ts.map +1 -0
- package/nugget/dist/policy.js +37 -0
- package/nugget/dist/policy.js.map +1 -0
- package/nugget/dist/redact.d.ts +26 -0
- package/nugget/dist/redact.d.ts.map +1 -0
- package/nugget/dist/redact.js +74 -0
- package/nugget/dist/redact.js.map +1 -0
- package/nugget/dist/tokens.d.ts +5 -0
- package/nugget/dist/tokens.d.ts.map +1 -0
- package/nugget/dist/tokens.js +18 -0
- package/nugget/dist/tokens.js.map +1 -0
- package/nugget/dist/transport.d.ts +30 -0
- package/nugget/dist/transport.d.ts.map +1 -0
- package/nugget/dist/transport.js +165 -0
- package/nugget/dist/transport.js.map +1 -0
- package/nugget/dist/types.d.ts +285 -0
- package/nugget/dist/types.d.ts.map +1 -0
- package/nugget/dist/types.js +4 -0
- package/nugget/dist/types.js.map +1 -0
- package/nugget/dist/util.d.ts +24 -0
- package/nugget/dist/util.d.ts.map +1 -0
- package/nugget/dist/util.js +101 -0
- package/nugget/dist/util.js.map +1 -0
- package/nugget/src/adapters/engines/anthropic.ts +62 -37
- package/nugget/src/adapters/engines/base.ts +50 -7
- package/nugget/src/adapters/engines/google.ts +92 -29
- package/nugget/src/adapters/engines/ollama.ts +51 -17
- package/nugget/src/adapters/engines/openaiChat.ts +61 -32
- package/nugget/src/adapters/profiles.ts +53 -3
- package/nugget/src/agent/loop.ts +158 -14
- package/nugget/src/connect.ts +48 -0
- package/nugget/src/errors.ts +25 -1
- package/nugget/src/handler.ts +286 -59
- package/nugget/src/index.ts +1 -0
- package/nugget/src/keys.ts +1 -5
- package/nugget/src/redact.ts +32 -1
- package/nugget/src/transport.ts +20 -1
- package/nugget/src/types.ts +96 -1
- package/nugget/src/util.ts +55 -0
- package/package.json +20 -6
package/dist/cjs/json.js
ADDED
|
@@ -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
|
+
}
|
package/dist/cjs/keys.js
ADDED
|
@@ -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,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
|
+
}
|
package/dist/cjs/util.js
ADDED
|
@@ -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
|