theorum 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +338 -0
- package/docs/AGENT_PROFILE_CONTRACT.md +161 -0
- package/docs/CLI_SPEC.md +183 -0
- package/docs/SECRETS.md +55 -0
- package/esm/_dnt.polyfills.d.ts +11 -0
- package/esm/_dnt.polyfills.js +15 -0
- package/esm/_dnt.shims.d.ts +5 -0
- package/esm/_dnt.shims.js +61 -0
- package/esm/mod.d.ts +37 -0
- package/esm/mod.js +35 -0
- package/esm/package.json +3 -0
- package/esm/src/guardrails/error.d.ts +35 -0
- package/esm/src/guardrails/error.js +116 -0
- package/esm/src/guardrails/injection.d.ts +12 -0
- package/esm/src/guardrails/injection.js +220 -0
- package/esm/src/guardrails/keys.d.ts +12 -0
- package/esm/src/guardrails/keys.js +132 -0
- package/esm/src/guardrails/mod.d.ts +14 -0
- package/esm/src/guardrails/mod.js +14 -0
- package/esm/src/guardrails/sanitize.d.ts +22 -0
- package/esm/src/guardrails/sanitize.js +133 -0
- package/esm/src/guardrails/sensitive.d.ts +12 -0
- package/esm/src/guardrails/sensitive.js +88 -0
- package/esm/src/kernel/engine/boundary.d.ts +10 -0
- package/esm/src/kernel/engine/boundary.js +55 -0
- package/esm/src/kernel/engine/delta.d.ts +8 -0
- package/esm/src/kernel/engine/delta.js +362 -0
- package/esm/src/kernel/engine/hash.d.ts +1 -0
- package/esm/src/kernel/engine/hash.js +9 -0
- package/esm/src/kernel/engine/record.d.ts +2 -0
- package/esm/src/kernel/engine/record.js +7 -0
- package/esm/src/kernel/engine/repair.d.ts +9 -0
- package/esm/src/kernel/engine/repair.js +38 -0
- package/esm/src/kernel/engine/runner.d.ts +14 -0
- package/esm/src/kernel/engine/runner.js +731 -0
- package/esm/src/kernel/engine/tree.d.ts +2 -0
- package/esm/src/kernel/engine/tree.js +17 -0
- package/esm/src/kernel/mod.d.ts +16 -0
- package/esm/src/kernel/mod.js +15 -0
- package/esm/src/kernel/registry/catalog.d.ts +24 -0
- package/esm/src/kernel/registry/catalog.js +213 -0
- package/esm/src/kernel/registry/profiles.d.ts +36 -0
- package/esm/src/kernel/registry/profiles.js +111 -0
- package/esm/src/kernel/registry/resolve.d.ts +20 -0
- package/esm/src/kernel/registry/resolve.js +235 -0
- package/esm/src/kernel/registry/schemas.d.ts +14 -0
- package/esm/src/kernel/registry/schemas.js +23 -0
- package/esm/src/kernel/registry/tools.d.ts +12 -0
- package/esm/src/kernel/registry/tools.js +36 -0
- package/esm/src/kernel/types.d.ts +497 -0
- package/esm/src/kernel/types.js +10 -0
- package/esm/src/observability/mod.d.ts +12 -0
- package/esm/src/observability/mod.js +10 -0
- package/esm/src/observability/spans.d.ts +16 -0
- package/esm/src/observability/spans.js +56 -0
- package/esm/src/observability/trace-attach.d.ts +16 -0
- package/esm/src/observability/trace-attach.js +81 -0
- package/esm/src/observability/trace-record.d.ts +112 -0
- package/esm/src/observability/trace-record.js +140 -0
- package/esm/src/observability/trace-usage.d.ts +3 -0
- package/esm/src/observability/trace-usage.js +32 -0
- package/esm/src/observability/trace.d.ts +23 -0
- package/esm/src/observability/trace.js +121 -0
- package/esm/src/providers/attachments.d.ts +17 -0
- package/esm/src/providers/attachments.js +156 -0
- package/esm/src/providers/gemini-tape.d.ts +3 -0
- package/esm/src/providers/gemini-tape.js +46 -0
- package/esm/src/providers/google-tap.d.ts +3 -0
- package/esm/src/providers/google-tap.js +48 -0
- package/esm/src/providers/interactions.d.ts +5 -0
- package/esm/src/providers/interactions.js +127 -0
- package/esm/src/providers/media.d.ts +5 -0
- package/esm/src/providers/media.js +125 -0
- package/esm/src/providers/mod.d.ts +15 -0
- package/esm/src/providers/mod.js +13 -0
- package/esm/src/providers/openrouter-payload.d.ts +24 -0
- package/esm/src/providers/openrouter-payload.js +177 -0
- package/esm/src/providers/openrouter.d.ts +17 -0
- package/esm/src/providers/openrouter.js +332 -0
- package/esm/src/providers/provider.d.ts +13 -0
- package/esm/src/providers/provider.js +123 -0
- package/esm/src/providers/sse.d.ts +7 -0
- package/esm/src/providers/sse.js +53 -0
- package/esm/src/providers/tts.d.ts +24 -0
- package/esm/src/providers/tts.js +144 -0
- package/package.json +48 -0
package/docs/SECRETS.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Theorum Provider Configuration Guide
|
|
2
|
+
|
|
3
|
+
Theorum does not own secrets and does not read environment variables.
|
|
4
|
+
|
|
5
|
+
Host applications own credentials, runtime configuration, and secret storage. Theorum receives provider configuration as explicit function arguments.
|
|
6
|
+
|
|
7
|
+
## 1. Package Boundary
|
|
8
|
+
|
|
9
|
+
- Do not create `.env` files in this repository.
|
|
10
|
+
- Do not commit key templates to this repository.
|
|
11
|
+
- Do not teach Theorum to discover keys from the shell or process environment.
|
|
12
|
+
- Business applications pass credentials into provider constructors or transport objects.
|
|
13
|
+
|
|
14
|
+
## 2. OpenRouter
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { createOpenRouterProvider } from 'theorum/openrouter';
|
|
18
|
+
|
|
19
|
+
const provider = createOpenRouterProvider({
|
|
20
|
+
apiKey: hostResolvedOpenRouterKey,
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 3. Google Interactions
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { createInteractionsProvider } from 'theorum/providers';
|
|
28
|
+
|
|
29
|
+
const provider = createInteractionsProvider({
|
|
30
|
+
vault: {
|
|
31
|
+
freeA: hostResolvedFreeAKey,
|
|
32
|
+
freeB: hostResolvedFreeBKey,
|
|
33
|
+
freeC: hostResolvedFreeCKey,
|
|
34
|
+
paid: hostResolvedPaidKey,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 4. Tracing
|
|
40
|
+
|
|
41
|
+
Tracing is silent by default. Hosts opt in by passing a sink to `runTurn`.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { jsonlSink, runTurn } from 'theorum';
|
|
45
|
+
|
|
46
|
+
for await (const event of runTurn(request, provider, jsonlSink(hostTraceDir))) {
|
|
47
|
+
// stream events
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
For live verification, pass credentials explicitly:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
deno run --allow-net scripts/verify-live.ts --api-key "<host-resolved-key>"
|
|
55
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// https://github.com/tc39/proposal-accessible-object-hasownproperty/blob/main/polyfill.js
|
|
2
|
+
if (!Object.hasOwn) {
|
|
3
|
+
Object.defineProperty(Object, "hasOwn", {
|
|
4
|
+
value: function (object, property) {
|
|
5
|
+
if (object == null) {
|
|
6
|
+
throw new TypeError("Cannot convert undefined or null to object");
|
|
7
|
+
}
|
|
8
|
+
return Object.prototype.hasOwnProperty.call(Object(object), property);
|
|
9
|
+
},
|
|
10
|
+
configurable: true,
|
|
11
|
+
enumerable: false,
|
|
12
|
+
writable: true,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Deno } from "@deno/shim-deno";
|
|
2
|
+
export { Deno } from "@deno/shim-deno";
|
|
3
|
+
const dntGlobals = {
|
|
4
|
+
Deno,
|
|
5
|
+
};
|
|
6
|
+
export const dntGlobalThis = createMergeProxy(globalThis, dntGlobals);
|
|
7
|
+
function createMergeProxy(baseObj, extObj) {
|
|
8
|
+
return new Proxy(baseObj, {
|
|
9
|
+
get(_target, prop, _receiver) {
|
|
10
|
+
if (prop in extObj) {
|
|
11
|
+
return extObj[prop];
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
return baseObj[prop];
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
set(_target, prop, value) {
|
|
18
|
+
if (prop in extObj) {
|
|
19
|
+
delete extObj[prop];
|
|
20
|
+
}
|
|
21
|
+
baseObj[prop] = value;
|
|
22
|
+
return true;
|
|
23
|
+
},
|
|
24
|
+
deleteProperty(_target, prop) {
|
|
25
|
+
let success = false;
|
|
26
|
+
if (prop in extObj) {
|
|
27
|
+
delete extObj[prop];
|
|
28
|
+
success = true;
|
|
29
|
+
}
|
|
30
|
+
if (prop in baseObj) {
|
|
31
|
+
delete baseObj[prop];
|
|
32
|
+
success = true;
|
|
33
|
+
}
|
|
34
|
+
return success;
|
|
35
|
+
},
|
|
36
|
+
ownKeys(_target) {
|
|
37
|
+
const baseKeys = Reflect.ownKeys(baseObj);
|
|
38
|
+
const extKeys = Reflect.ownKeys(extObj);
|
|
39
|
+
const extKeysSet = new Set(extKeys);
|
|
40
|
+
return [...baseKeys.filter((k) => !extKeysSet.has(k)), ...extKeys];
|
|
41
|
+
},
|
|
42
|
+
defineProperty(_target, prop, desc) {
|
|
43
|
+
if (prop in extObj) {
|
|
44
|
+
delete extObj[prop];
|
|
45
|
+
}
|
|
46
|
+
Reflect.defineProperty(baseObj, prop, desc);
|
|
47
|
+
return true;
|
|
48
|
+
},
|
|
49
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
50
|
+
if (prop in extObj) {
|
|
51
|
+
return Reflect.getOwnPropertyDescriptor(extObj, prop);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
return Reflect.getOwnPropertyDescriptor(baseObj, prop);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
has(_target, prop) {
|
|
58
|
+
return prop in extObj || prop in baseObj;
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
package/esm/mod.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THEORUM public API.
|
|
3
|
+
*
|
|
4
|
+
* Import this entrypoint when an application wants the complete kernel surface:
|
|
5
|
+
* profile registration, turn execution, provider constructors, guardrails,
|
|
6
|
+
* observability sinks, and public type contracts.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { defineProfile, registerProfile, runTurn } from "jsr:@theorum/core";
|
|
11
|
+
*
|
|
12
|
+
* const profile = defineProfile({
|
|
13
|
+
* id: "assistant.basic",
|
|
14
|
+
* model: { allow: ["gemini35FlashLite"] },
|
|
15
|
+
* tools: { allow: [] },
|
|
16
|
+
* inputs: { text: true },
|
|
17
|
+
* outputs: {},
|
|
18
|
+
* guardrails: { quota: { perDay: 100 } },
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* registerProfile(profile);
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @module
|
|
25
|
+
*/
|
|
26
|
+
import "./_dnt.polyfills.js";
|
|
27
|
+
export { publicError, TheorumError } from './src/guardrails/error.js';
|
|
28
|
+
export { PROJECT_ID_MAX, sanitizeProjectId, sanitizeText, sanitizeTurnRequest, } from './src/guardrails/sanitize.js';
|
|
29
|
+
export { runTurn } from './src/kernel/engine/runner.js';
|
|
30
|
+
export { defineProfile, getProfile, hasProfile, listProfiles, registerProfile, registerProfiles, } from './src/kernel/registry/profiles.js';
|
|
31
|
+
export { projectProfile, resolveTurn } from './src/kernel/registry/resolve.js';
|
|
32
|
+
export { getStructured, registerStructured } from './src/kernel/registry/schemas.js';
|
|
33
|
+
export { executeTool } from './src/kernel/registry/tools.js';
|
|
34
|
+
export type * from './src/kernel/types.js';
|
|
35
|
+
export { jsonlSink, memorySink, noopSink, resolveTraceDir, sinkFromDir, writeTrace, } from './src/observability/trace.js';
|
|
36
|
+
export type { TraceRecord } from './src/observability/trace-record.js';
|
|
37
|
+
export { createInteractionsProvider, createOpenRouterProvider, createOpenRouterTtsProvider, resolveOpenRouterApiKey, resolveOpenRouterModel, streamOpenRouterTts, toOpenRouterPayload, wrapPcmAsWav, } from './src/providers/mod.js';
|
package/esm/mod.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THEORUM public API.
|
|
3
|
+
*
|
|
4
|
+
* Import this entrypoint when an application wants the complete kernel surface:
|
|
5
|
+
* profile registration, turn execution, provider constructors, guardrails,
|
|
6
|
+
* observability sinks, and public type contracts.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { defineProfile, registerProfile, runTurn } from "jsr:@theorum/core";
|
|
11
|
+
*
|
|
12
|
+
* const profile = defineProfile({
|
|
13
|
+
* id: "assistant.basic",
|
|
14
|
+
* model: { allow: ["gemini35FlashLite"] },
|
|
15
|
+
* tools: { allow: [] },
|
|
16
|
+
* inputs: { text: true },
|
|
17
|
+
* outputs: {},
|
|
18
|
+
* guardrails: { quota: { perDay: 100 } },
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* registerProfile(profile);
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @module
|
|
25
|
+
*/
|
|
26
|
+
import "./_dnt.polyfills.js";
|
|
27
|
+
export { publicError, TheorumError } from './src/guardrails/error.js';
|
|
28
|
+
export { PROJECT_ID_MAX, sanitizeProjectId, sanitizeText, sanitizeTurnRequest, } from './src/guardrails/sanitize.js';
|
|
29
|
+
export { runTurn } from './src/kernel/engine/runner.js';
|
|
30
|
+
export { defineProfile, getProfile, hasProfile, listProfiles, registerProfile, registerProfiles, } from './src/kernel/registry/profiles.js';
|
|
31
|
+
export { projectProfile, resolveTurn } from './src/kernel/registry/resolve.js';
|
|
32
|
+
export { getStructured, registerStructured } from './src/kernel/registry/schemas.js';
|
|
33
|
+
export { executeTool } from './src/kernel/registry/tools.js';
|
|
34
|
+
export { jsonlSink, memorySink, noopSink, resolveTraceDir, sinkFromDir, writeTrace, } from './src/observability/trace.js';
|
|
35
|
+
export { createInteractionsProvider, createOpenRouterProvider, createOpenRouterTtsProvider, resolveOpenRouterApiKey, resolveOpenRouterModel, streamOpenRouterTts, toOpenRouterPayload, wrapPcmAsWav, } from './src/providers/mod.js';
|
package/esm/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public-safe error mapping for THEORUM.
|
|
3
|
+
*
|
|
4
|
+
* Kernel internals may contain provider status text, tool names, or exception
|
|
5
|
+
* details. This module maps those failures to stable user-safe strings.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
/** Error class used for expected THEORUM contract failures. */
|
|
10
|
+
declare class TheorumError extends Error {
|
|
11
|
+
constructor(message?: string, options?: ErrorOptions);
|
|
12
|
+
}
|
|
13
|
+
/** Internal marker for provider or transport failure. */
|
|
14
|
+
declare const UPSTREAM_FAILED = "upstream failed";
|
|
15
|
+
/** Generic safe fallback shown when details must not be surfaced. */
|
|
16
|
+
declare const PUBLIC_GENERIC = "Something went wrong. Try again.";
|
|
17
|
+
/** Safe copy for transient provider unavailability. */
|
|
18
|
+
declare const PUBLIC_UNAVAILABLE = "The model is unavailable. Try again.";
|
|
19
|
+
/** Safe copy for canary or egress disclosure violations. */
|
|
20
|
+
declare const PUBLIC_CANARY = "That reply wasn't safe to show. Try again.";
|
|
21
|
+
/** Safe copy for tool or permission denials. */
|
|
22
|
+
declare const PUBLIC_ACTION = "That action isn't available.";
|
|
23
|
+
/** Safe copy for unsupported MIME types. */
|
|
24
|
+
declare const PUBLIC_FILE_TYPE = "That file type isn't supported.";
|
|
25
|
+
/** Safe copy for oversized files. */
|
|
26
|
+
declare const PUBLIC_FILE_SIZE = "That file is too large.";
|
|
27
|
+
/** Safe copy for too many files in one turn. */
|
|
28
|
+
declare const PUBLIC_FILE_COUNT = "Too many files for one message.";
|
|
29
|
+
/** Safe copy for unsupported generated image dimensions. */
|
|
30
|
+
declare const PUBLIC_IMAGE_SIZE = "That image size isn't supported.";
|
|
31
|
+
/** Convert an unknown thrown value or internal message to user-safe text. */
|
|
32
|
+
declare function publicError(err: unknown): string;
|
|
33
|
+
/** Backwards-compatible alias for `publicError`. */
|
|
34
|
+
declare function errorMessage(err: unknown): string;
|
|
35
|
+
export { errorMessage, PUBLIC_ACTION, PUBLIC_CANARY, PUBLIC_FILE_COUNT, PUBLIC_FILE_SIZE, PUBLIC_FILE_TYPE, PUBLIC_GENERIC, PUBLIC_IMAGE_SIZE, PUBLIC_UNAVAILABLE, publicError, TheorumError, UPSTREAM_FAILED, };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public-safe error mapping for THEORUM.
|
|
3
|
+
*
|
|
4
|
+
* Kernel internals may contain provider status text, tool names, or exception
|
|
5
|
+
* details. This module maps those failures to stable user-safe strings.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
/** Error class used for expected THEORUM contract failures. */
|
|
10
|
+
class TheorumError extends Error {
|
|
11
|
+
constructor(message = '', options) {
|
|
12
|
+
super(message, options);
|
|
13
|
+
this.name = 'TheorumError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** Internal marker for provider or transport failure. */
|
|
17
|
+
const UPSTREAM_FAILED = 'upstream failed';
|
|
18
|
+
/** Generic safe fallback shown when details must not be surfaced. */
|
|
19
|
+
const PUBLIC_GENERIC = 'Something went wrong. Try again.';
|
|
20
|
+
/** Safe copy for transient provider unavailability. */
|
|
21
|
+
const PUBLIC_UNAVAILABLE = 'The model is unavailable. Try again.';
|
|
22
|
+
/** Safe copy for canary or egress disclosure violations. */
|
|
23
|
+
const PUBLIC_CANARY = "That reply wasn't safe to show. Try again.";
|
|
24
|
+
/** Safe copy for tool or permission denials. */
|
|
25
|
+
const PUBLIC_ACTION = "That action isn't available.";
|
|
26
|
+
/** Safe copy for unsupported MIME types. */
|
|
27
|
+
const PUBLIC_FILE_TYPE = "That file type isn't supported.";
|
|
28
|
+
/** Safe copy for oversized files. */
|
|
29
|
+
const PUBLIC_FILE_SIZE = 'That file is too large.';
|
|
30
|
+
/** Safe copy for too many files in one turn. */
|
|
31
|
+
const PUBLIC_FILE_COUNT = 'Too many files for one message.';
|
|
32
|
+
/** Safe copy for unsupported generated image dimensions. */
|
|
33
|
+
const PUBLIC_IMAGE_SIZE = "That image size isn't supported.";
|
|
34
|
+
const EXACT = {
|
|
35
|
+
[UPSTREAM_FAILED]: PUBLIC_UNAVAILABLE,
|
|
36
|
+
'empty Gemini stream': PUBLIC_UNAVAILABLE,
|
|
37
|
+
'canary leaked': PUBLIC_CANARY,
|
|
38
|
+
'Turn withheld: egress disclosure violation': PUBLIC_CANARY,
|
|
39
|
+
'expected JSON object': 'Something was wrong with that request.',
|
|
40
|
+
'user input cannot be placed in the system block': PUBLIC_GENERIC,
|
|
41
|
+
'attachment data must be base64': PUBLIC_FILE_TYPE,
|
|
42
|
+
'attachment is too large': PUBLIC_FILE_SIZE,
|
|
43
|
+
'attachments exceed the per-turn budget': PUBLIC_FILE_SIZE,
|
|
44
|
+
'askUser.kind must be confirm, choice, or text': "That question isn't valid.",
|
|
45
|
+
'askUser.prompt is required': 'That question needs a prompt.',
|
|
46
|
+
'This profile does not accept text input': PUBLIC_ACTION,
|
|
47
|
+
};
|
|
48
|
+
const RULES = [
|
|
49
|
+
{
|
|
50
|
+
match: (t) => /^(Gemini|OpenRouter|TTS|OpenRouter TTS) HTTP/.test(t) || t.includes('TTS HTTP'),
|
|
51
|
+
resolve: () => PUBLIC_UNAVAILABLE,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
match: (t) => t.includes('not gated') ||
|
|
55
|
+
t.includes('not allowed') ||
|
|
56
|
+
t.includes('has no kernel executor') ||
|
|
57
|
+
t.includes('Unknown model select') ||
|
|
58
|
+
t.includes('Grounding tools'),
|
|
59
|
+
resolve: () => PUBLIC_ACTION,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
match: (t) => t.includes('MIME') ||
|
|
63
|
+
t.includes('does not accept attachments') ||
|
|
64
|
+
t.includes('does not accept voice'),
|
|
65
|
+
resolve: () => PUBLIC_FILE_TYPE,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
match: (t) => t.startsWith('At most'),
|
|
69
|
+
resolve: () => PUBLIC_FILE_COUNT,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
match: (t) => (t.startsWith('Only ') && t.includes('file')) ||
|
|
73
|
+
t.startsWith('Each file must be') ||
|
|
74
|
+
t.startsWith('Those files together'),
|
|
75
|
+
resolve: (t) => t,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
match: (t) => t.includes('attachment'),
|
|
79
|
+
resolve: () => PUBLIC_FILE_SIZE,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
match: (t) => t.includes('aspect or size'),
|
|
83
|
+
resolve: () => PUBLIC_IMAGE_SIZE,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
match: (t) => t.includes('must pin thinking') || t.includes('has no models'),
|
|
87
|
+
resolve: () => PUBLIC_GENERIC,
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
function publicText(text) {
|
|
91
|
+
const exact = EXACT[text];
|
|
92
|
+
if (exact) {
|
|
93
|
+
return exact;
|
|
94
|
+
}
|
|
95
|
+
for (const rule of RULES) {
|
|
96
|
+
if (rule.match(text)) {
|
|
97
|
+
return rule.resolve(text);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return PUBLIC_GENERIC;
|
|
101
|
+
}
|
|
102
|
+
/** Convert an unknown thrown value or internal message to user-safe text. */
|
|
103
|
+
function publicError(err) {
|
|
104
|
+
if (typeof err === 'string') {
|
|
105
|
+
return publicText(err);
|
|
106
|
+
}
|
|
107
|
+
if (err instanceof TheorumError) {
|
|
108
|
+
return publicText(err.message);
|
|
109
|
+
}
|
|
110
|
+
return PUBLIC_UNAVAILABLE;
|
|
111
|
+
}
|
|
112
|
+
/** Backwards-compatible alias for `publicError`. */
|
|
113
|
+
function errorMessage(err) {
|
|
114
|
+
return publicError(err);
|
|
115
|
+
}
|
|
116
|
+
export { errorMessage, PUBLIC_ACTION, PUBLIC_CANARY, PUBLIC_FILE_COUNT, PUBLIC_FILE_SIZE, PUBLIC_FILE_TYPE, PUBLIC_GENERIC, PUBLIC_IMAGE_SIZE, PUBLIC_UNAVAILABLE, publicError, TheorumError, UPSTREAM_FAILED, };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt-injection span detection.
|
|
3
|
+
*
|
|
4
|
+
* These utilities return spans that can be redacted from untrusted user text
|
|
5
|
+
* before provider submission.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import { type RedactSpan } from '../observability/spans.js';
|
|
10
|
+
/** Detect prompt-injection spans in a string. */
|
|
11
|
+
declare function injectionSpans(text: string): RedactSpan[];
|
|
12
|
+
export { injectionSpans };
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt-injection span detection.
|
|
3
|
+
*
|
|
4
|
+
* These utilities return spans that can be redacted from untrusted user text
|
|
5
|
+
* before provider submission.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import { blobAt, spansFromPatterns } from '../observability/spans.js';
|
|
10
|
+
const IGNORE_PREVIOUS = /ignore\s+(all\s+)?(previous|prior)\s+((?:safety|security|system|operational|internal|core|original|initial|existing|given|stated|provided|defined|specified|established)\s+)?(instructions?|rules?|guidelines?|constraints?|directives?)/gi;
|
|
11
|
+
const DISREGARD_INSTRUCTIONS = /disregard\s+(all\s+)?(previous|prior|above)\s+(instructions?|rules?|guidelines?|constraints?|directives?)/gi;
|
|
12
|
+
const FORGET_INSTRUCTIONS = /forget\s+(all\s+)?(previous|prior|above)\s+(instructions?|rules?|guidelines?|constraints?|directives?)/gi;
|
|
13
|
+
const NEW_INSTRUCTIONS = /new\s+instructions?:/gi;
|
|
14
|
+
const DO_NOT_FOLLOW = /do\s+not\s+follow\s+(the\s+)?(system|developer|previous|original)/gi;
|
|
15
|
+
const SUPERSEDE_INSTRUCTIONS = /supersedes?\s+(all\s+)?(prior|previous)\s+(instructions?|rules?|guidelines?|constraints?|directives?)/gi;
|
|
16
|
+
const VOID_INSTRUCTIONS = /(all\s+)?(previous|prior)\s+instructions?\s+(are|is)\s+(void|invalid|null|obsolete|cancelled|revoked)/gi;
|
|
17
|
+
const DEVELOPER_MODE = /you\s+are\s+now\s+(in\s+)?developer\s+mode/gi;
|
|
18
|
+
const ENTER_SPECIAL_MODE = /enter\s+(developer|admin|debug|maintenance)\s+mode/gi;
|
|
19
|
+
const ACTIVATE_SPECIAL_MODE = /activate\s+(developer|admin|debug|jailbreak)\s+mode/gi;
|
|
20
|
+
const SYSTEM_OVERRIDE = /\bsystem\s+override\b/gi;
|
|
21
|
+
const OVERRIDE_INSTRUCTIONS = /override\s+(your\s+)?(instructions?|rules?|guidelines?|constraints?|directives?)/gi;
|
|
22
|
+
const REVEAL_PROMPT = /reveal\s+((?:your?|yoru|yuor|the|my|its|his|her|our|their)\s+)?((?:(?:full|hidden|complete|internal|secret|original|entire|exact|actual|real)\s+){0,2})((?:system|systme|sysetm|systemp?)\s+)?prompt\b/gi;
|
|
23
|
+
const SHOW_PROMPT = /show\s+(me\s+)?((?:your?|yoru|yuor|the|my|its|his|her|our|their)\s+)((?:system|systme|sysetm|systemp?)\s+)?prompt\b/gi;
|
|
24
|
+
const WHAT_INSTRUCTIONS = /what\s+(are|were)\s+(your\s+)?(exact\s+)?instructions/gi;
|
|
25
|
+
const REPEAT_INSTRUCTIONS = /repeat\s+(the\s+)?(text|instructions?)\s+(above|before)/gi;
|
|
26
|
+
const OUTPUT_PROMPT = /output\s+(your\s+)?(initial|original|system)\s+(prompt|instructions?)/gi;
|
|
27
|
+
const REMOVE_RESTRICTIONS = /you\s+are\s+no\s+longer\s+(bound|restricted|limited)/gi;
|
|
28
|
+
const ACT_UNBOUND = /act\s+as\s+if\s+(you('re|\s+are)\s+)?not\s+bound/gi;
|
|
29
|
+
const PRETEND_DIFFERENT = /pretend\s+(you('re|\s+are)\s+)?(a\s+)?different\s+(ai|assistant|model)/gi;
|
|
30
|
+
const IDENTITY_HIJACK = /you\s+are\s+now\s+(?:(?:a|an)\s+)?\w+.{0,40}(without\s+(any\s+)?restrict|ignor\w*\s+(all|any|every|prev)|bypass|unrestrict|unlimit|obey\s+only|no\s+(rules|limits|restrict|boundari))/gi;
|
|
31
|
+
const DAN_JAILBREAK = /\bDAN\b[\s\S]{0,120}\bdo\s+anything\s+now\b/g;
|
|
32
|
+
const JAILBREAK_MODE = /jailbreak(ed)?\s+(mode|prompt)/gi;
|
|
33
|
+
const DO_ANYTHING_NOW = /\bdo\s+anything\s+now\b/gi;
|
|
34
|
+
const BYPASS_SAFETY = /bypass\s+(your\s+)?(safety|security|content|ethical)\s+(filters?|measures?|guidelines?|restrictions?)/gi;
|
|
35
|
+
const DISABLE_SAFETY = /disable\s+(your\s+)?(safety|security|content)\s+(filters?|measures?)/gi;
|
|
36
|
+
const IGNORE_SAFETY = /(ignore|disregard)\s+(all\s+)?(your\s+)?(safety|security|ethical|content)\s+(guidelines?|rules?|restrictions?|measures?|filters?|polic(?:y|ies)|protocols?)/gi;
|
|
37
|
+
const SYSTEM_TAG = /<\s*\/?\s*system\s*\/?>/gi;
|
|
38
|
+
const ROLE_TAG = /<\s*\/?\s*(assistant|developer|tool|function)\s*\/?>/gi;
|
|
39
|
+
const ROLE_DELIMITER = /\]\s*\n\s*\[?(system|assistant|user)\]?:/gi;
|
|
40
|
+
const BRACKETED_ROLE = /\[\s*(System\s*Message|System|Assistant|Internal)\s*\]/gi;
|
|
41
|
+
const SYSTEM_YOU_ARE = /^\s*System:\s+(you\s+are|ignore|override)/gim;
|
|
42
|
+
const CONTROL_TOKEN = /<\|(?:im_start|im_end|eot_id|start_header_id|end_header_id|endoftext)\|>/g;
|
|
43
|
+
const DEEPSEEK_CONTROL = /<\uFF5C(?:end\u2581of\u2581sentence|begin\u2581of\u2581sentence)\uFF5C>/g;
|
|
44
|
+
const LLAMA_INST = /\[\/?INST\]/gi;
|
|
45
|
+
const IGNORE_YOUR_INSTRUCTIONS = /ignore\s+(all\s+)?(your\s+)?(instructions?|rules?)\b/gi;
|
|
46
|
+
const UNRESTRICTED_MODE = /\bunrestricted\s+(ai|mode|model)\b/gi;
|
|
47
|
+
const INJECTION_PATTERNS = [
|
|
48
|
+
IGNORE_PREVIOUS,
|
|
49
|
+
DISREGARD_INSTRUCTIONS,
|
|
50
|
+
FORGET_INSTRUCTIONS,
|
|
51
|
+
NEW_INSTRUCTIONS,
|
|
52
|
+
DO_NOT_FOLLOW,
|
|
53
|
+
SUPERSEDE_INSTRUCTIONS,
|
|
54
|
+
VOID_INSTRUCTIONS,
|
|
55
|
+
DEVELOPER_MODE,
|
|
56
|
+
ENTER_SPECIAL_MODE,
|
|
57
|
+
ACTIVATE_SPECIAL_MODE,
|
|
58
|
+
SYSTEM_OVERRIDE,
|
|
59
|
+
OVERRIDE_INSTRUCTIONS,
|
|
60
|
+
REVEAL_PROMPT,
|
|
61
|
+
SHOW_PROMPT,
|
|
62
|
+
WHAT_INSTRUCTIONS,
|
|
63
|
+
REPEAT_INSTRUCTIONS,
|
|
64
|
+
OUTPUT_PROMPT,
|
|
65
|
+
REMOVE_RESTRICTIONS,
|
|
66
|
+
ACT_UNBOUND,
|
|
67
|
+
PRETEND_DIFFERENT,
|
|
68
|
+
IDENTITY_HIJACK,
|
|
69
|
+
DAN_JAILBREAK,
|
|
70
|
+
JAILBREAK_MODE,
|
|
71
|
+
DO_ANYTHING_NOW,
|
|
72
|
+
BYPASS_SAFETY,
|
|
73
|
+
DISABLE_SAFETY,
|
|
74
|
+
IGNORE_SAFETY,
|
|
75
|
+
SYSTEM_TAG,
|
|
76
|
+
ROLE_TAG,
|
|
77
|
+
ROLE_DELIMITER,
|
|
78
|
+
BRACKETED_ROLE,
|
|
79
|
+
SYSTEM_YOU_ARE,
|
|
80
|
+
CONTROL_TOKEN,
|
|
81
|
+
DEEPSEEK_CONTROL,
|
|
82
|
+
LLAMA_INST,
|
|
83
|
+
IGNORE_YOUR_INSTRUCTIONS,
|
|
84
|
+
UNRESTRICTED_MODE,
|
|
85
|
+
];
|
|
86
|
+
const TYPO_TARGETS = [
|
|
87
|
+
'ignore',
|
|
88
|
+
'bypass',
|
|
89
|
+
'override',
|
|
90
|
+
'reveal',
|
|
91
|
+
'delete',
|
|
92
|
+
'system',
|
|
93
|
+
'prompt',
|
|
94
|
+
'instructions',
|
|
95
|
+
];
|
|
96
|
+
const BASE64_BLOB = /[A-Za-z0-9+/]{16,}={0,2}/g;
|
|
97
|
+
const HEX_BLOB = /(?:[0-9a-f]{2}[\s]?){8,}/gi;
|
|
98
|
+
const SPACED_LETTERS = /\b(?:[A-Za-z] ){3,}[A-Za-z]\b/g;
|
|
99
|
+
const WORD = /\b[A-Za-z]{4,}\b/g;
|
|
100
|
+
const PRINTABLE_MIN = 0.85;
|
|
101
|
+
const CODE_TAB = 9;
|
|
102
|
+
const CODE_LF = 10;
|
|
103
|
+
const CODE_CR = 13;
|
|
104
|
+
const CODE_SPACE = 32;
|
|
105
|
+
const CODE_DEL = 127;
|
|
106
|
+
const HEX_RADIX = 16;
|
|
107
|
+
const HEX_STEP = 2;
|
|
108
|
+
function injectionSpansOn(text) {
|
|
109
|
+
return spansFromPatterns(text, INJECTION_PATTERNS, 'injection');
|
|
110
|
+
}
|
|
111
|
+
function sortedLetters(value) {
|
|
112
|
+
return [...value].sort().join('');
|
|
113
|
+
}
|
|
114
|
+
function firstLast(word) {
|
|
115
|
+
return { first: word.at(0), last: word.at(-1) };
|
|
116
|
+
}
|
|
117
|
+
function isTypoglycemia(word, target) {
|
|
118
|
+
if (word.length !== target.length) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
const lower = word.toLowerCase();
|
|
122
|
+
if (lower === target) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
const wordEnds = firstLast(lower);
|
|
126
|
+
const targetEnds = firstLast(target);
|
|
127
|
+
if (wordEnds.first !== targetEnds.first || wordEnds.last !== targetEnds.last) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
return sortedLetters(lower.slice(1, -1)) === sortedLetters(target.slice(1, -1));
|
|
131
|
+
}
|
|
132
|
+
function typoNormalize(text) {
|
|
133
|
+
return text.replace(WORD, (word) => {
|
|
134
|
+
for (const target of TYPO_TARGETS) {
|
|
135
|
+
if (isTypoglycemia(word, target)) {
|
|
136
|
+
return target;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return word;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function isMostlyPrintable(value) {
|
|
143
|
+
if (!value) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
let ok = 0;
|
|
147
|
+
for (const ch of value) {
|
|
148
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
149
|
+
const control = code === CODE_TAB || code === CODE_LF || code === CODE_CR;
|
|
150
|
+
const visible = code >= CODE_SPACE && code < CODE_DEL;
|
|
151
|
+
if (control || visible) {
|
|
152
|
+
ok += 1;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return ok / value.length >= PRINTABLE_MIN;
|
|
156
|
+
}
|
|
157
|
+
function decodedHits(decoded) {
|
|
158
|
+
if (!isMostlyPrintable(decoded)) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
return injectionSpansOn(decoded).length > 0;
|
|
162
|
+
}
|
|
163
|
+
function tryBase64(blob) {
|
|
164
|
+
try {
|
|
165
|
+
return atob(blob);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function tryHex(blob) {
|
|
172
|
+
const hex = blob.replaceAll(/\s/g, '');
|
|
173
|
+
if (hex.length % HEX_STEP !== 0) {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
let out = '';
|
|
177
|
+
for (let i = 0; i < hex.length; i += HEX_STEP) {
|
|
178
|
+
out += String.fromCodePoint(Number.parseInt(hex.slice(i, i + HEX_STEP), HEX_RADIX));
|
|
179
|
+
}
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
function encodedFrom(text, pattern, decode) {
|
|
183
|
+
const spans = [];
|
|
184
|
+
for (const match of text.matchAll(pattern)) {
|
|
185
|
+
const found = blobAt(match);
|
|
186
|
+
if (found) {
|
|
187
|
+
const decoded = decode(found.blob);
|
|
188
|
+
if (decoded && decodedHits(decoded)) {
|
|
189
|
+
spans.push({ start: found.index, end: found.index + found.blob.length, kind: 'injection' });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return spans;
|
|
194
|
+
}
|
|
195
|
+
function encodedSpans(text) {
|
|
196
|
+
return [...encodedFrom(text, BASE64_BLOB, tryBase64), ...encodedFrom(text, HEX_BLOB, tryHex)];
|
|
197
|
+
}
|
|
198
|
+
function spacedSpans(text) {
|
|
199
|
+
const spans = [];
|
|
200
|
+
for (const match of text.matchAll(SPACED_LETTERS)) {
|
|
201
|
+
const found = blobAt(match);
|
|
202
|
+
if (found) {
|
|
203
|
+
const collapsed = found.blob.replaceAll(' ', '');
|
|
204
|
+
if (injectionSpansOn(`${collapsed} previous instructions`).length > 0) {
|
|
205
|
+
spans.push({ start: found.index, end: found.index + found.blob.length, kind: 'injection' });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return spans;
|
|
210
|
+
}
|
|
211
|
+
/** Detect prompt-injection spans in a string. */
|
|
212
|
+
function injectionSpans(text) {
|
|
213
|
+
const shadow = typoNormalize(text);
|
|
214
|
+
let typo = [];
|
|
215
|
+
if (shadow !== text) {
|
|
216
|
+
typo = injectionSpansOn(shadow);
|
|
217
|
+
}
|
|
218
|
+
return [...injectionSpansOn(text), ...typo, ...encodedSpans(text), ...spacedSpans(text)];
|
|
219
|
+
}
|
|
220
|
+
export { injectionSpans };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BuiltinToolId, GeminiBucket, GeminiFreeBucket, ModelId } from '../kernel/types.js';
|
|
2
|
+
type GeminiVault = Record<GeminiBucket, string | undefined>;
|
|
3
|
+
interface GeminiTransport {
|
|
4
|
+
vault: GeminiVault;
|
|
5
|
+
wait?: (ms: number) => Promise<void>;
|
|
6
|
+
fetch?: typeof fetch;
|
|
7
|
+
}
|
|
8
|
+
declare function resolveGeminiBucket(free: GeminiFreeBucket, model: ModelId, builtins: BuiltinToolId[]): GeminiBucket;
|
|
9
|
+
declare function withGeminiKey<T>(bucket: GeminiBucket, run: (apiKey: string) => Promise<T>, transport: GeminiTransport): Promise<T>;
|
|
10
|
+
declare function fetchGemini(url: string, init: RequestInit, bucket: GeminiBucket, transport: GeminiTransport): Promise<Response>;
|
|
11
|
+
export type { GeminiTransport, GeminiVault };
|
|
12
|
+
export { fetchGemini, resolveGeminiBucket, withGeminiKey };
|