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 +21 -0
- package/README.md +197 -0
- package/dist/index.d.mts +114 -0
- package/dist/index.d.ts +114 -0
- package/dist/index.js +288 -0
- package/dist/index.mjs +260 -0
- package/package.json +63 -0
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
|
package/dist/index.d.mts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|