prompt-identifiers-baml 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License Copyright (c) 2026 Leon Wolf
2
+
3
+ Permission is hereby granted, free of
4
+ charge, to any person obtaining a copy of this software and associated
5
+ documentation files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use, copy, modify, merge,
7
+ publish, distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice
12
+ (including the next paragraph) shall be included in all copies or substantial
13
+ portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
16
+ ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
18
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
19
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,197 @@
1
+ # prompt-identifiers-baml
2
+
3
+ BAML wrapper for automatic ID encoding/decoding in LLM function calls. Reduces token usage by up to 90% for UUIDs and ULIDs.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install prompt-identifiers-baml prompt-identifiers @boundaryml/baml
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { wrapBamlFunction } from 'prompt-identifiers-baml';
15
+ import { b } from './baml_client';
16
+
17
+ // Wrap your BAML function
18
+ const analyzeUser = wrapBamlFunction(b.AnalyzeUser, {
19
+ config: { inputFormat: 'UUID', outputFormat: 'SafeNumeric' },
20
+ });
21
+
22
+ // Use normally - IDs are automatically encoded/decoded
23
+ const result = await analyzeUser({
24
+ user_id: '123e4567-e89b-42d3-a456-426655440000',
25
+ items: [
26
+ { id: '987fcdeb-51a2-43f7-8d9c-0123456789ab', name: 'Order 1' },
27
+ ],
28
+ });
29
+
30
+ // The BAML function receives:
31
+ // { user_id: '<000>', items: [{ id: '<001>', name: 'Order 1' }] }
32
+ //
33
+ // You receive the response with original UUIDs restored
34
+ ```
35
+
36
+ ## How It Works
37
+
38
+ 1. **Before BAML call**: Deep traverses input object and encodes all ID fields
39
+ - `123e4567-e89b-42d3-a456-426655440000` → `<000>`
40
+
41
+ 2. **After BAML call**: Deep traverses output object and decodes all placeholders
42
+ - `<000>` → `123e4567-e89b-42d3-a456-426655440000`
43
+
44
+ This is completely transparent - you work with real IDs, the LLM works with compact placeholders.
45
+
46
+ ## Configuration
47
+
48
+ ```typescript
49
+ wrapBamlFunction(fn, {
50
+ // Required: encoding configuration
51
+ config: {
52
+ inputFormat: 'UUID', // or 'ULID' or custom RegExp
53
+ outputFormat: 'SafeNumeric', // or 'Numeric', 'IdToken', { template: '...' }
54
+ },
55
+
56
+ // Optional: specify which fields to encode
57
+ // If not provided, all matching strings are encoded
58
+ encodeFields: ['user_id', 'items[].id', 'metadata.owner_id'],
59
+
60
+ // Optional: callbacks for logging/debugging
61
+ onEncode: (result) => {
62
+ console.log(`Encoded ${result.encodedCount} IDs`);
63
+ console.log('Mapping:', result.mapping);
64
+ },
65
+ onDecode: (result) => {
66
+ console.log(`Decoded ${result.decodedCount} placeholders`);
67
+ },
68
+ });
69
+ ```
70
+
71
+ ### Field Path Syntax
72
+
73
+ The `encodeFields` option supports dot notation and array wildcards:
74
+
75
+ | Pattern | Description | Example Match |
76
+ |---------|-------------|---------------|
77
+ | `user_id` | Top-level field | `{ user_id: '...' }` |
78
+ | `data.user_id` | Nested field | `{ data: { user_id: '...' } }` |
79
+ | `items[].id` | Array item field | `{ items: [{ id: '...' }] }` |
80
+ | `data.users[].profile.id` | Deep nested | `{ data: { users: [{ profile: { id: '...' } }] } }` |
81
+
82
+ ### Input Formats
83
+
84
+ | Format | Description | Example |
85
+ |--------|-------------|---------|
86
+ | `'UUID'` | RFC 4122 UUIDs | `123e4567-e89b-42d3-a456-426655440000` |
87
+ | `'ULID'` | Crockford Base32 ULIDs | `01ARZ3NDEKTSV4RRFFQ69G5FAV` |
88
+ | `RegExp` | Custom pattern | `/user-\d{6}/gi` |
89
+
90
+ ### Output Formats
91
+
92
+ | Format | Description | Example |
93
+ |--------|-------------|---------|
94
+ | `'SafeNumeric'` | Collision-safe with angle brackets (recommended) | `<000>`, `<001>` |
95
+ | `'Numeric'` | Simple numeric with smart triplet expansion | `000`, `001` |
96
+ | `'IdToken'` | Base62 encoding | `0`, `A`, `z`, `10` |
97
+ | `{ template: '...' }` | Custom template | `{ template: '[ID:{i}]' }` → `[ID:0]` |
98
+
99
+ ## Streaming Support
100
+
101
+ Use `wrapBamlStreamingFunction` for BAML streaming functions:
102
+
103
+ ```typescript
104
+ import { wrapBamlStreamingFunction } from 'prompt-identifiers-baml';
105
+ import { b } from './baml_client';
106
+
107
+ const streamAnalysis = wrapBamlStreamingFunction(b.stream.AnalyzeUser, {
108
+ config: { inputFormat: 'UUID', outputFormat: 'SafeNumeric' },
109
+ });
110
+
111
+ // IDs are decoded in real-time as partials arrive
112
+ for await (const partial of streamAnalysis({ user_id: uuid })) {
113
+ console.log(partial);
114
+ }
115
+ ```
116
+
117
+ ## Utility Functions
118
+
119
+ ### encodeObject
120
+
121
+ Manually encode an object (useful for custom integrations):
122
+
123
+ ```typescript
124
+ import { encodeObject } from 'prompt-identifiers-baml';
125
+
126
+ const { encoded, mapping } = encodeObject(
127
+ { user_id: 'uuid-here', data: { owner: 'other-uuid' } },
128
+ { inputFormat: 'UUID', outputFormat: 'SafeNumeric' }
129
+ );
130
+
131
+ // encoded: { user_id: '<000>', data: { owner: '<001>' } }
132
+ // mapping: { '<000>': 'uuid-here', '<001>': 'other-uuid' }
133
+ ```
134
+
135
+ ### decodeObject
136
+
137
+ Manually decode an object:
138
+
139
+ ```typescript
140
+ import { decodeObject } from 'prompt-identifiers-baml';
141
+
142
+ const decoded = decodeObject(
143
+ { user_id: '<000>', summary: 'User <000> is active' },
144
+ { '<000>': 'uuid-here' }
145
+ );
146
+
147
+ // decoded: { user_id: 'uuid-here', summary: 'User uuid-here is active' }
148
+ ```
149
+
150
+ ## Type Safety
151
+
152
+ The wrapper preserves BAML's TypeScript types:
153
+
154
+ ```typescript
155
+ // BAML-generated types are preserved
156
+ const analyzeUser = wrapBamlFunction(b.AnalyzeUser, { config });
157
+
158
+ // TypeScript knows the input/output types
159
+ const result: AnalyzeUserOutput = await analyzeUser({
160
+ user_id: '...', // Type-checked
161
+ items: [...],
162
+ });
163
+ ```
164
+
165
+ ## Auto-Detection vs Explicit Fields
166
+
167
+ **Auto-detection mode** (no `encodeFields`):
168
+ - Encodes ALL string fields matching the input pattern
169
+ - Simple to use, works for most cases
170
+ - May encode fields you don't want encoded
171
+
172
+ **Explicit fields** (with `encodeFields`):
173
+ - Only encodes specified fields
174
+ - More precise control
175
+ - Recommended for production use
176
+
177
+ ```typescript
178
+ // Auto-detection: all UUIDs encoded
179
+ const wrapped1 = wrapBamlFunction(fn, {
180
+ config: { inputFormat: 'UUID', outputFormat: 'SafeNumeric' },
181
+ });
182
+
183
+ // Explicit: only user_id and item IDs encoded
184
+ const wrapped2 = wrapBamlFunction(fn, {
185
+ config: { inputFormat: 'UUID', outputFormat: 'SafeNumeric' },
186
+ encodeFields: ['user_id', 'items[].id'],
187
+ });
188
+ ```
189
+
190
+ ## Peer Dependencies
191
+
192
+ - `prompt-identifiers` >= 0.1.0
193
+ - `@boundaryml/baml` >= 0.70.0 (optional)
194
+
195
+ ## License
196
+
197
+ MIT
@@ -0,0 +1,114 @@
1
+ import { EncodeConfig } from 'prompt-identifiers';
2
+
3
+ /**
4
+ * prompt-identifiers-baml - BAML wrapper for automatic ID encoding/decoding
5
+ *
6
+ * Wraps BAML-generated TypeScript functions to automatically encode IDs
7
+ * in inputs and decode them in outputs.
8
+ */
9
+
10
+ /** Configuration options for the BAML wrapper */
11
+ interface WrapBamlFunctionOptions {
12
+ /** Encoding configuration (inputFormat and outputFormat) */
13
+ config: EncodeConfig;
14
+ /**
15
+ * Optional: specific field paths to encode.
16
+ * If not provided, all string fields matching the input pattern are encoded.
17
+ *
18
+ * Supports dot notation and array wildcards:
19
+ * - 'user_id' - top-level field
20
+ * - 'data.user_id' - nested field
21
+ * - 'items[].id' - all 'id' fields in 'items' array
22
+ * - 'data.users[].profile.id' - deeply nested array field
23
+ *
24
+ * @example
25
+ * encodeFields: ['user_id', 'items[].id', 'metadata.owner_id']
26
+ */
27
+ encodeFields?: string[];
28
+ /**
29
+ * Optional callback fired after encoding IDs in the input.
30
+ */
31
+ onEncode?: (result: {
32
+ mapping: Record<string, string>;
33
+ encodedCount: number;
34
+ }) => void;
35
+ /**
36
+ * Optional callback fired after decoding IDs in the output.
37
+ */
38
+ onDecode?: (result: {
39
+ decodedCount: number;
40
+ }) => void;
41
+ }
42
+ /** A BAML function type (sync or async) */
43
+ type BamlFunction<TInput, TOutput> = (input: TInput) => Promise<TOutput>;
44
+ /** A BAML streaming function type */
45
+ type BamlStreamingFunction<TInput, TPartial, TFinal> = (input: TInput) => AsyncGenerator<TPartial, TFinal, unknown>;
46
+ /**
47
+ * Wrap a BAML function to automatically encode IDs in inputs and decode them in outputs.
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * import { wrapBamlFunction } from 'prompt-identifiers-baml';
52
+ * import { b } from './baml_client';
53
+ *
54
+ * const analyzeUser = wrapBamlFunction(b.AnalyzeUser, {
55
+ * config: { inputFormat: 'UUID', outputFormat: 'SafeNumeric' },
56
+ * encodeFields: ['user_id', 'items[].id'], // Optional: specific fields
57
+ * });
58
+ *
59
+ * // Use normally - IDs are auto-encoded/decoded
60
+ * const result = await analyzeUser({
61
+ * user_id: '123e4567-e89b-42d3-a456-426655440000',
62
+ * items: [{ id: '987fcdeb-51a2-43f7-8d9c-0123456789ab', name: 'test' }]
63
+ * });
64
+ * ```
65
+ */
66
+ declare function wrapBamlFunction<TInput, TOutput>(fn: BamlFunction<TInput, TOutput>, options: WrapBamlFunctionOptions): BamlFunction<TInput, TOutput>;
67
+ /**
68
+ * Wrap a BAML streaming function to automatically encode IDs in inputs
69
+ * and decode them in outputs (both partial and final).
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * import { wrapBamlStreamingFunction } from 'prompt-identifiers-baml';
74
+ * import { b } from './baml_client';
75
+ *
76
+ * const streamAnalysis = wrapBamlStreamingFunction(b.stream.AnalyzeUser, {
77
+ * config: { inputFormat: 'UUID', outputFormat: 'SafeNumeric' },
78
+ * });
79
+ *
80
+ * for await (const partial of streamAnalysis({ user_id: 'uuid-here' })) {
81
+ * console.log(partial); // IDs decoded in real-time
82
+ * }
83
+ * ```
84
+ */
85
+ declare function wrapBamlStreamingFunction<TInput, TPartial, TFinal>(fn: BamlStreamingFunction<TInput, TPartial, TFinal>, options: WrapBamlFunctionOptions): BamlStreamingFunction<TInput, TPartial, TFinal>;
86
+ /**
87
+ * Utility function to encode a plain object (useful for manual encoding).
88
+ *
89
+ * @example
90
+ * ```typescript
91
+ * const { encoded, mapping } = encodeObject(
92
+ * { user_id: 'uuid-here', data: { owner: 'other-uuid' } },
93
+ * { inputFormat: 'UUID', outputFormat: 'SafeNumeric' }
94
+ * );
95
+ * ```
96
+ */
97
+ declare function encodeObject<T>(obj: T, config: EncodeConfig, encodeFields?: string[]): {
98
+ encoded: T;
99
+ mapping: Record<string, string>;
100
+ };
101
+ /**
102
+ * Utility function to decode a plain object (useful for manual decoding).
103
+ *
104
+ * @example
105
+ * ```typescript
106
+ * const decoded = decodeObject(
107
+ * { user_id: '«000»', summary: 'User «000» is active' },
108
+ * { '«000»': 'uuid-here' }
109
+ * );
110
+ * ```
111
+ */
112
+ declare function decodeObject<T>(obj: T, mapping: Record<string, string>): T;
113
+
114
+ export { type BamlFunction, type BamlStreamingFunction, type WrapBamlFunctionOptions, decodeObject, encodeObject, wrapBamlFunction, wrapBamlStreamingFunction };
@@ -0,0 +1,114 @@
1
+ import { EncodeConfig } from 'prompt-identifiers';
2
+
3
+ /**
4
+ * prompt-identifiers-baml - BAML wrapper for automatic ID encoding/decoding
5
+ *
6
+ * Wraps BAML-generated TypeScript functions to automatically encode IDs
7
+ * in inputs and decode them in outputs.
8
+ */
9
+
10
+ /** Configuration options for the BAML wrapper */
11
+ interface WrapBamlFunctionOptions {
12
+ /** Encoding configuration (inputFormat and outputFormat) */
13
+ config: EncodeConfig;
14
+ /**
15
+ * Optional: specific field paths to encode.
16
+ * If not provided, all string fields matching the input pattern are encoded.
17
+ *
18
+ * Supports dot notation and array wildcards:
19
+ * - 'user_id' - top-level field
20
+ * - 'data.user_id' - nested field
21
+ * - 'items[].id' - all 'id' fields in 'items' array
22
+ * - 'data.users[].profile.id' - deeply nested array field
23
+ *
24
+ * @example
25
+ * encodeFields: ['user_id', 'items[].id', 'metadata.owner_id']
26
+ */
27
+ encodeFields?: string[];
28
+ /**
29
+ * Optional callback fired after encoding IDs in the input.
30
+ */
31
+ onEncode?: (result: {
32
+ mapping: Record<string, string>;
33
+ encodedCount: number;
34
+ }) => void;
35
+ /**
36
+ * Optional callback fired after decoding IDs in the output.
37
+ */
38
+ onDecode?: (result: {
39
+ decodedCount: number;
40
+ }) => void;
41
+ }
42
+ /** A BAML function type (sync or async) */
43
+ type BamlFunction<TInput, TOutput> = (input: TInput) => Promise<TOutput>;
44
+ /** A BAML streaming function type */
45
+ type BamlStreamingFunction<TInput, TPartial, TFinal> = (input: TInput) => AsyncGenerator<TPartial, TFinal, unknown>;
46
+ /**
47
+ * Wrap a BAML function to automatically encode IDs in inputs and decode them in outputs.
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * import { wrapBamlFunction } from 'prompt-identifiers-baml';
52
+ * import { b } from './baml_client';
53
+ *
54
+ * const analyzeUser = wrapBamlFunction(b.AnalyzeUser, {
55
+ * config: { inputFormat: 'UUID', outputFormat: 'SafeNumeric' },
56
+ * encodeFields: ['user_id', 'items[].id'], // Optional: specific fields
57
+ * });
58
+ *
59
+ * // Use normally - IDs are auto-encoded/decoded
60
+ * const result = await analyzeUser({
61
+ * user_id: '123e4567-e89b-42d3-a456-426655440000',
62
+ * items: [{ id: '987fcdeb-51a2-43f7-8d9c-0123456789ab', name: 'test' }]
63
+ * });
64
+ * ```
65
+ */
66
+ declare function wrapBamlFunction<TInput, TOutput>(fn: BamlFunction<TInput, TOutput>, options: WrapBamlFunctionOptions): BamlFunction<TInput, TOutput>;
67
+ /**
68
+ * Wrap a BAML streaming function to automatically encode IDs in inputs
69
+ * and decode them in outputs (both partial and final).
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * import { wrapBamlStreamingFunction } from 'prompt-identifiers-baml';
74
+ * import { b } from './baml_client';
75
+ *
76
+ * const streamAnalysis = wrapBamlStreamingFunction(b.stream.AnalyzeUser, {
77
+ * config: { inputFormat: 'UUID', outputFormat: 'SafeNumeric' },
78
+ * });
79
+ *
80
+ * for await (const partial of streamAnalysis({ user_id: 'uuid-here' })) {
81
+ * console.log(partial); // IDs decoded in real-time
82
+ * }
83
+ * ```
84
+ */
85
+ declare function wrapBamlStreamingFunction<TInput, TPartial, TFinal>(fn: BamlStreamingFunction<TInput, TPartial, TFinal>, options: WrapBamlFunctionOptions): BamlStreamingFunction<TInput, TPartial, TFinal>;
86
+ /**
87
+ * Utility function to encode a plain object (useful for manual encoding).
88
+ *
89
+ * @example
90
+ * ```typescript
91
+ * const { encoded, mapping } = encodeObject(
92
+ * { user_id: 'uuid-here', data: { owner: 'other-uuid' } },
93
+ * { inputFormat: 'UUID', outputFormat: 'SafeNumeric' }
94
+ * );
95
+ * ```
96
+ */
97
+ declare function encodeObject<T>(obj: T, config: EncodeConfig, encodeFields?: string[]): {
98
+ encoded: T;
99
+ mapping: Record<string, string>;
100
+ };
101
+ /**
102
+ * Utility function to decode a plain object (useful for manual decoding).
103
+ *
104
+ * @example
105
+ * ```typescript
106
+ * const decoded = decodeObject(
107
+ * { user_id: '«000»', summary: 'User «000» is active' },
108
+ * { '«000»': 'uuid-here' }
109
+ * );
110
+ * ```
111
+ */
112
+ declare function decodeObject<T>(obj: T, mapping: Record<string, string>): T;
113
+
114
+ export { type BamlFunction, type BamlStreamingFunction, type WrapBamlFunctionOptions, decodeObject, encodeObject, wrapBamlFunction, wrapBamlStreamingFunction };
package/dist/index.js ADDED
@@ -0,0 +1,288 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ decodeObject: () => decodeObject,
24
+ encodeObject: () => encodeObject,
25
+ wrapBamlFunction: () => wrapBamlFunction,
26
+ wrapBamlStreamingFunction: () => wrapBamlStreamingFunction
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+ var import_prompt_identifiers = require("prompt-identifiers");
30
+ function parseFieldPath(path) {
31
+ const segments = [];
32
+ let current = "";
33
+ for (let i = 0; i < path.length; i++) {
34
+ const char = path[i];
35
+ if (char === ".") {
36
+ if (current) {
37
+ segments.push(current);
38
+ current = "";
39
+ }
40
+ } else if (char === "[" && path[i + 1] === "]") {
41
+ if (current) {
42
+ segments.push(current);
43
+ current = "";
44
+ }
45
+ segments.push("[]");
46
+ i++;
47
+ } else {
48
+ current += char;
49
+ }
50
+ }
51
+ if (current) {
52
+ segments.push(current);
53
+ }
54
+ return segments;
55
+ }
56
+ function matchesFieldPath(currentPath, targetSegments) {
57
+ if (currentPath.length !== targetSegments.length) {
58
+ return false;
59
+ }
60
+ for (let i = 0; i < targetSegments.length; i++) {
61
+ const target = targetSegments[i];
62
+ const current = currentPath[i];
63
+ if (target === "[]") {
64
+ if (!/^\d+$/.test(current)) {
65
+ return false;
66
+ }
67
+ } else if (target !== current) {
68
+ return false;
69
+ }
70
+ }
71
+ return true;
72
+ }
73
+ function getInputPattern(inputFormat) {
74
+ if (inputFormat instanceof RegExp) {
75
+ const flags = inputFormat.flags.includes("g") ? inputFormat.flags : inputFormat.flags + "g";
76
+ return new RegExp(inputFormat.source, flags);
77
+ }
78
+ if (inputFormat === "UUID") {
79
+ return /\b[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b/gi;
80
+ }
81
+ return /\b[0-9A-HJKMNP-TV-Z]{26}\b/gi;
82
+ }
83
+ function formatPlaceholder(outputFormat, index) {
84
+ if (outputFormat === "SafeNumeric") {
85
+ const s = index.toString();
86
+ const width = Math.max(3, Math.ceil(s.length / 3) * 3);
87
+ return `<${s.padStart(width, "0")}>`;
88
+ }
89
+ if (outputFormat === "Numeric") {
90
+ const s = index.toString();
91
+ const width = Math.max(3, Math.ceil(s.length / 3) * 3);
92
+ return s.padStart(width, "0");
93
+ }
94
+ if (outputFormat === "IdToken") {
95
+ const BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
96
+ if (index < 62) return BASE62[index];
97
+ let result = "";
98
+ let n = index;
99
+ while (n > 0) {
100
+ result = BASE62[n % 62] + result;
101
+ n = Math.floor(n / 62);
102
+ }
103
+ return result;
104
+ }
105
+ if (outputFormat === "Passthrough") {
106
+ return "";
107
+ }
108
+ if (typeof outputFormat === "function") {
109
+ return outputFormat(index);
110
+ }
111
+ const template = outputFormat.template;
112
+ const match = template.match(/\{i(?::([^}]+))?\}/);
113
+ if (!match) return `${index}`;
114
+ const specifier = match[1];
115
+ let formatted;
116
+ if (!specifier) {
117
+ formatted = index.toString();
118
+ } else if (specifier === "base62") {
119
+ const BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
120
+ if (index < 62) formatted = BASE62[index];
121
+ else {
122
+ formatted = "";
123
+ let n = index;
124
+ while (n > 0) {
125
+ formatted = BASE62[n % 62] + formatted;
126
+ n = Math.floor(n / 62);
127
+ }
128
+ }
129
+ } else if (specifier === "zeroFilled") {
130
+ const s = index.toString();
131
+ const width = Math.max(3, Math.ceil(s.length / 3) * 3);
132
+ formatted = s.padStart(width, "0");
133
+ } else {
134
+ const padMatch = specifier.match(/^(\d+)$/);
135
+ if (padMatch) {
136
+ formatted = index.toString().padStart(parseInt(padMatch[1], 10), "0");
137
+ } else {
138
+ formatted = index.toString();
139
+ }
140
+ }
141
+ return template.replace(match[0], formatted);
142
+ }
143
+ function encodeStringWithContext(text, ctx) {
144
+ if (ctx.config.outputFormat === "Passthrough") {
145
+ return text;
146
+ }
147
+ const pattern = getInputPattern(ctx.config.inputFormat);
148
+ pattern.lastIndex = 0;
149
+ return text.replace(pattern, (match) => {
150
+ const id = match.toLowerCase();
151
+ if (ctx.idToPlaceholder.has(id)) {
152
+ return ctx.idToPlaceholder.get(id);
153
+ }
154
+ const placeholder = formatPlaceholder(ctx.config.outputFormat, ctx.nextIndex);
155
+ ctx.nextIndex++;
156
+ ctx.idToPlaceholder.set(id, placeholder);
157
+ ctx.mapping[placeholder] = id;
158
+ return placeholder;
159
+ });
160
+ }
161
+ function deepEncode(value, ctx, path = []) {
162
+ if (value === null || value === void 0) {
163
+ return value;
164
+ }
165
+ if (typeof value === "string") {
166
+ const shouldEncode = ctx.fieldPaths === null || ctx.fieldPaths.some((fp) => matchesFieldPath(path, fp));
167
+ if (!shouldEncode) {
168
+ return value;
169
+ }
170
+ return encodeStringWithContext(value, ctx);
171
+ }
172
+ if (Array.isArray(value)) {
173
+ return value.map(
174
+ (item, index) => deepEncode(item, ctx, [...path, String(index)])
175
+ );
176
+ }
177
+ if (typeof value === "object") {
178
+ const result = {};
179
+ for (const [key, val] of Object.entries(value)) {
180
+ result[key] = deepEncode(val, ctx, [...path, key]);
181
+ }
182
+ return result;
183
+ }
184
+ return value;
185
+ }
186
+ function deepDecode(value, mapping, countRef) {
187
+ if (value === null || value === void 0) {
188
+ return value;
189
+ }
190
+ if (typeof value === "string") {
191
+ const decoded = (0, import_prompt_identifiers.decode)(value, mapping);
192
+ if (decoded !== value) {
193
+ countRef.count++;
194
+ }
195
+ return decoded;
196
+ }
197
+ if (Array.isArray(value)) {
198
+ return value.map((item) => deepDecode(item, mapping, countRef));
199
+ }
200
+ if (typeof value === "object") {
201
+ const result = {};
202
+ for (const [key, val] of Object.entries(value)) {
203
+ result[key] = deepDecode(val, mapping, countRef);
204
+ }
205
+ return result;
206
+ }
207
+ return value;
208
+ }
209
+ function wrapBamlFunction(fn, options) {
210
+ const { config, encodeFields, onEncode, onDecode } = options;
211
+ const fieldPaths = encodeFields ? encodeFields.map(parseFieldPath) : null;
212
+ return async (input) => {
213
+ const ctx = {
214
+ config,
215
+ fieldPaths,
216
+ idToPlaceholder: /* @__PURE__ */ new Map(),
217
+ mapping: {},
218
+ nextIndex: 0
219
+ };
220
+ const encodedInput = deepEncode(input, ctx);
221
+ onEncode?.({
222
+ mapping: ctx.mapping,
223
+ encodedCount: Object.keys(ctx.mapping).length
224
+ });
225
+ const output = await fn(encodedInput);
226
+ const countRef = { count: 0 };
227
+ const decodedOutput = deepDecode(output, ctx.mapping, countRef);
228
+ onDecode?.({ decodedCount: countRef.count });
229
+ return decodedOutput;
230
+ };
231
+ }
232
+ function wrapBamlStreamingFunction(fn, options) {
233
+ const { config, encodeFields, onEncode, onDecode } = options;
234
+ const fieldPaths = encodeFields ? encodeFields.map(parseFieldPath) : null;
235
+ return async function* (input) {
236
+ const ctx = {
237
+ config,
238
+ fieldPaths,
239
+ idToPlaceholder: /* @__PURE__ */ new Map(),
240
+ mapping: {},
241
+ nextIndex: 0
242
+ };
243
+ const encodedInput = deepEncode(input, ctx);
244
+ onEncode?.({
245
+ mapping: ctx.mapping,
246
+ encodedCount: Object.keys(ctx.mapping).length
247
+ });
248
+ const generator = fn(encodedInput);
249
+ let totalDecoded = 0;
250
+ while (true) {
251
+ const { value, done } = await generator.next();
252
+ if (done) {
253
+ const countRef2 = { count: 0 };
254
+ const decodedValue2 = deepDecode(value, ctx.mapping, countRef2);
255
+ totalDecoded += countRef2.count;
256
+ onDecode?.({ decodedCount: totalDecoded });
257
+ return decodedValue2;
258
+ }
259
+ const countRef = { count: 0 };
260
+ const decodedValue = deepDecode(value, ctx.mapping, countRef);
261
+ totalDecoded += countRef.count;
262
+ yield decodedValue;
263
+ }
264
+ };
265
+ }
266
+ function encodeObject(obj, config, encodeFields) {
267
+ const fieldPaths = encodeFields ? encodeFields.map(parseFieldPath) : null;
268
+ const ctx = {
269
+ config,
270
+ fieldPaths,
271
+ idToPlaceholder: /* @__PURE__ */ new Map(),
272
+ mapping: {},
273
+ nextIndex: 0
274
+ };
275
+ const encoded = deepEncode(obj, ctx);
276
+ return { encoded, mapping: ctx.mapping };
277
+ }
278
+ function decodeObject(obj, mapping) {
279
+ const countRef = { count: 0 };
280
+ return deepDecode(obj, mapping, countRef);
281
+ }
282
+ // Annotate the CommonJS export names for ESM import in node:
283
+ 0 && (module.exports = {
284
+ decodeObject,
285
+ encodeObject,
286
+ wrapBamlFunction,
287
+ wrapBamlStreamingFunction
288
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,260 @@
1
+ // src/index.ts
2
+ import { decode } from "prompt-identifiers";
3
+ function parseFieldPath(path) {
4
+ const segments = [];
5
+ let current = "";
6
+ for (let i = 0; i < path.length; i++) {
7
+ const char = path[i];
8
+ if (char === ".") {
9
+ if (current) {
10
+ segments.push(current);
11
+ current = "";
12
+ }
13
+ } else if (char === "[" && path[i + 1] === "]") {
14
+ if (current) {
15
+ segments.push(current);
16
+ current = "";
17
+ }
18
+ segments.push("[]");
19
+ i++;
20
+ } else {
21
+ current += char;
22
+ }
23
+ }
24
+ if (current) {
25
+ segments.push(current);
26
+ }
27
+ return segments;
28
+ }
29
+ function matchesFieldPath(currentPath, targetSegments) {
30
+ if (currentPath.length !== targetSegments.length) {
31
+ return false;
32
+ }
33
+ for (let i = 0; i < targetSegments.length; i++) {
34
+ const target = targetSegments[i];
35
+ const current = currentPath[i];
36
+ if (target === "[]") {
37
+ if (!/^\d+$/.test(current)) {
38
+ return false;
39
+ }
40
+ } else if (target !== current) {
41
+ return false;
42
+ }
43
+ }
44
+ return true;
45
+ }
46
+ function getInputPattern(inputFormat) {
47
+ if (inputFormat instanceof RegExp) {
48
+ const flags = inputFormat.flags.includes("g") ? inputFormat.flags : inputFormat.flags + "g";
49
+ return new RegExp(inputFormat.source, flags);
50
+ }
51
+ if (inputFormat === "UUID") {
52
+ return /\b[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b/gi;
53
+ }
54
+ return /\b[0-9A-HJKMNP-TV-Z]{26}\b/gi;
55
+ }
56
+ function formatPlaceholder(outputFormat, index) {
57
+ if (outputFormat === "SafeNumeric") {
58
+ const s = index.toString();
59
+ const width = Math.max(3, Math.ceil(s.length / 3) * 3);
60
+ return `<${s.padStart(width, "0")}>`;
61
+ }
62
+ if (outputFormat === "Numeric") {
63
+ const s = index.toString();
64
+ const width = Math.max(3, Math.ceil(s.length / 3) * 3);
65
+ return s.padStart(width, "0");
66
+ }
67
+ if (outputFormat === "IdToken") {
68
+ const BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
69
+ if (index < 62) return BASE62[index];
70
+ let result = "";
71
+ let n = index;
72
+ while (n > 0) {
73
+ result = BASE62[n % 62] + result;
74
+ n = Math.floor(n / 62);
75
+ }
76
+ return result;
77
+ }
78
+ if (outputFormat === "Passthrough") {
79
+ return "";
80
+ }
81
+ if (typeof outputFormat === "function") {
82
+ return outputFormat(index);
83
+ }
84
+ const template = outputFormat.template;
85
+ const match = template.match(/\{i(?::([^}]+))?\}/);
86
+ if (!match) return `${index}`;
87
+ const specifier = match[1];
88
+ let formatted;
89
+ if (!specifier) {
90
+ formatted = index.toString();
91
+ } else if (specifier === "base62") {
92
+ const BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
93
+ if (index < 62) formatted = BASE62[index];
94
+ else {
95
+ formatted = "";
96
+ let n = index;
97
+ while (n > 0) {
98
+ formatted = BASE62[n % 62] + formatted;
99
+ n = Math.floor(n / 62);
100
+ }
101
+ }
102
+ } else if (specifier === "zeroFilled") {
103
+ const s = index.toString();
104
+ const width = Math.max(3, Math.ceil(s.length / 3) * 3);
105
+ formatted = s.padStart(width, "0");
106
+ } else {
107
+ const padMatch = specifier.match(/^(\d+)$/);
108
+ if (padMatch) {
109
+ formatted = index.toString().padStart(parseInt(padMatch[1], 10), "0");
110
+ } else {
111
+ formatted = index.toString();
112
+ }
113
+ }
114
+ return template.replace(match[0], formatted);
115
+ }
116
+ function encodeStringWithContext(text, ctx) {
117
+ if (ctx.config.outputFormat === "Passthrough") {
118
+ return text;
119
+ }
120
+ const pattern = getInputPattern(ctx.config.inputFormat);
121
+ pattern.lastIndex = 0;
122
+ return text.replace(pattern, (match) => {
123
+ const id = match.toLowerCase();
124
+ if (ctx.idToPlaceholder.has(id)) {
125
+ return ctx.idToPlaceholder.get(id);
126
+ }
127
+ const placeholder = formatPlaceholder(ctx.config.outputFormat, ctx.nextIndex);
128
+ ctx.nextIndex++;
129
+ ctx.idToPlaceholder.set(id, placeholder);
130
+ ctx.mapping[placeholder] = id;
131
+ return placeholder;
132
+ });
133
+ }
134
+ function deepEncode(value, ctx, path = []) {
135
+ if (value === null || value === void 0) {
136
+ return value;
137
+ }
138
+ if (typeof value === "string") {
139
+ const shouldEncode = ctx.fieldPaths === null || ctx.fieldPaths.some((fp) => matchesFieldPath(path, fp));
140
+ if (!shouldEncode) {
141
+ return value;
142
+ }
143
+ return encodeStringWithContext(value, ctx);
144
+ }
145
+ if (Array.isArray(value)) {
146
+ return value.map(
147
+ (item, index) => deepEncode(item, ctx, [...path, String(index)])
148
+ );
149
+ }
150
+ if (typeof value === "object") {
151
+ const result = {};
152
+ for (const [key, val] of Object.entries(value)) {
153
+ result[key] = deepEncode(val, ctx, [...path, key]);
154
+ }
155
+ return result;
156
+ }
157
+ return value;
158
+ }
159
+ function deepDecode(value, mapping, countRef) {
160
+ if (value === null || value === void 0) {
161
+ return value;
162
+ }
163
+ if (typeof value === "string") {
164
+ const decoded = decode(value, mapping);
165
+ if (decoded !== value) {
166
+ countRef.count++;
167
+ }
168
+ return decoded;
169
+ }
170
+ if (Array.isArray(value)) {
171
+ return value.map((item) => deepDecode(item, mapping, countRef));
172
+ }
173
+ if (typeof value === "object") {
174
+ const result = {};
175
+ for (const [key, val] of Object.entries(value)) {
176
+ result[key] = deepDecode(val, mapping, countRef);
177
+ }
178
+ return result;
179
+ }
180
+ return value;
181
+ }
182
+ function wrapBamlFunction(fn, options) {
183
+ const { config, encodeFields, onEncode, onDecode } = options;
184
+ const fieldPaths = encodeFields ? encodeFields.map(parseFieldPath) : null;
185
+ return async (input) => {
186
+ const ctx = {
187
+ config,
188
+ fieldPaths,
189
+ idToPlaceholder: /* @__PURE__ */ new Map(),
190
+ mapping: {},
191
+ nextIndex: 0
192
+ };
193
+ const encodedInput = deepEncode(input, ctx);
194
+ onEncode?.({
195
+ mapping: ctx.mapping,
196
+ encodedCount: Object.keys(ctx.mapping).length
197
+ });
198
+ const output = await fn(encodedInput);
199
+ const countRef = { count: 0 };
200
+ const decodedOutput = deepDecode(output, ctx.mapping, countRef);
201
+ onDecode?.({ decodedCount: countRef.count });
202
+ return decodedOutput;
203
+ };
204
+ }
205
+ function wrapBamlStreamingFunction(fn, options) {
206
+ const { config, encodeFields, onEncode, onDecode } = options;
207
+ const fieldPaths = encodeFields ? encodeFields.map(parseFieldPath) : null;
208
+ return async function* (input) {
209
+ const ctx = {
210
+ config,
211
+ fieldPaths,
212
+ idToPlaceholder: /* @__PURE__ */ new Map(),
213
+ mapping: {},
214
+ nextIndex: 0
215
+ };
216
+ const encodedInput = deepEncode(input, ctx);
217
+ onEncode?.({
218
+ mapping: ctx.mapping,
219
+ encodedCount: Object.keys(ctx.mapping).length
220
+ });
221
+ const generator = fn(encodedInput);
222
+ let totalDecoded = 0;
223
+ while (true) {
224
+ const { value, done } = await generator.next();
225
+ if (done) {
226
+ const countRef2 = { count: 0 };
227
+ const decodedValue2 = deepDecode(value, ctx.mapping, countRef2);
228
+ totalDecoded += countRef2.count;
229
+ onDecode?.({ decodedCount: totalDecoded });
230
+ return decodedValue2;
231
+ }
232
+ const countRef = { count: 0 };
233
+ const decodedValue = deepDecode(value, ctx.mapping, countRef);
234
+ totalDecoded += countRef.count;
235
+ yield decodedValue;
236
+ }
237
+ };
238
+ }
239
+ function encodeObject(obj, config, encodeFields) {
240
+ const fieldPaths = encodeFields ? encodeFields.map(parseFieldPath) : null;
241
+ const ctx = {
242
+ config,
243
+ fieldPaths,
244
+ idToPlaceholder: /* @__PURE__ */ new Map(),
245
+ mapping: {},
246
+ nextIndex: 0
247
+ };
248
+ const encoded = deepEncode(obj, ctx);
249
+ return { encoded, mapping: ctx.mapping };
250
+ }
251
+ function decodeObject(obj, mapping) {
252
+ const countRef = { count: 0 };
253
+ return deepDecode(obj, mapping, countRef);
254
+ }
255
+ export {
256
+ decodeObject,
257
+ encodeObject,
258
+ wrapBamlFunction,
259
+ wrapBamlStreamingFunction
260
+ };
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "prompt-identifiers-baml",
3
+ "version": "0.1.0",
4
+ "description": "BAML integration for prompt-identifiers. Efficient ID compression for token optimization.",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/index.d.mts",
12
+ "default": "./dist/index.mjs"
13
+ },
14
+ "require": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ }
18
+ }
19
+ },
20
+ "sideEffects": false,
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "keywords": [
25
+ "llm",
26
+ "ai",
27
+ "baml",
28
+ "boundaryml",
29
+ "tokens",
30
+ "compression",
31
+ "prompt",
32
+ "uuid",
33
+ "ulid"
34
+ ],
35
+ "author": "Leon Wolf <fogxdev@gmail.com>",
36
+ "license": "MIT",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/fogx/prompt-identifiers"
40
+ },
41
+ "peerDependencies": {
42
+ "prompt-identifiers": ">=0.1.0",
43
+ "@boundaryml/baml": ">=0.70.0"
44
+ },
45
+ "peerDependenciesMeta": {
46
+ "@boundaryml/baml": {
47
+ "optional": true
48
+ }
49
+ },
50
+ "devDependencies": {
51
+ "@types/jest": "^29.0.0",
52
+ "@types/node": "^20.0.0",
53
+ "jest": "^29.0.0",
54
+ "ts-jest": "^29.0.0",
55
+ "tsup": "^8.5.1",
56
+ "typescript": "^5.0.0",
57
+ "prompt-identifiers": "0.1.0"
58
+ },
59
+ "scripts": {
60
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
61
+ "test": "jest"
62
+ }
63
+ }