openclaw-langcache 1.1.0 → 1.2.1

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.
@@ -1,528 +0,0 @@
1
- #!/usr/bin/env bash
2
- #
3
- # langcache.sh - CLI wrapper for Redis LangCache REST API
4
- # Enforces default caching policy with hard blocks
5
- #
6
- # Usage:
7
- # langcache.sh search <prompt> [--threshold <0.0-1.0>] [--attr <key=value>] [--strategy <exact,semantic>]
8
- # langcache.sh store <prompt> <response> [--attr <key=value>]...
9
- # langcache.sh delete --id <entry-id>
10
- # langcache.sh delete --attr <key=value>
11
- # langcache.sh flush
12
- # langcache.sh check <text> # Check if text would be blocked
13
- #
14
- # Environment variables:
15
- # LANGCACHE_HOST - LangCache API host (required)
16
- # LANGCACHE_CACHE_ID - Cache ID (required)
17
- # LANGCACHE_API_KEY - API key (required)
18
-
19
- set -euo pipefail
20
-
21
- # Load secrets if available
22
- SECRETS_FILE="${HOME}/.openclaw/secrets.env"
23
- if [[ -f "$SECRETS_FILE" ]]; then
24
- # shellcheck source=/dev/null
25
- source "$SECRETS_FILE"
26
- fi
27
-
28
- # =============================================================================
29
- # HARD BLOCK PATTERNS - These NEVER get cached
30
- # =============================================================================
31
-
32
- # Temporal patterns (time-sensitive)
33
- TEMPORAL_PATTERNS=(
34
- '\b(today|tomorrow|tonight|yesterday)\b'
35
- '\b(this|next|last)[[:space:]]+(week|month|year|monday|tuesday|wednesday|thursday|friday|saturday|sunday)\b'
36
- '\bin[[:space:]]+[0-9]+[[:space:]]+(minutes?|hours?|days?|weeks?)\b'
37
- '\b(deadline|eta|appointment|scheduled?)\b'
38
- '\b(right[[:space:]]+now|at[[:space:]]+[0-9]{1,2}(:[0-9]{2})?[[:space:]]*(am|pm)?)\b'
39
- '\bmeeting[[:space:]]+at\b'
40
- )
41
-
42
- # Credential patterns (security risk)
43
- CREDENTIAL_PATTERNS=(
44
- '\b(api[_-]?key|api[_-]?secret|access[_-]?token)\b'
45
- '\b(password|passwd|pwd)[[:space:]]*[:=]'
46
- '\b(secret[_-]?key|private[_-]?key)\b'
47
- '\b(otp|2fa|totp|authenticator)[[:space:]]*(code|token)?\b'
48
- '\bbearer[[:space:]]+[a-zA-Z0-9_-]+'
49
- '\b(sk|pk)[_-][a-zA-Z0-9]{20,}\b'
50
- )
51
-
52
- # Identifier patterns (PII)
53
- IDENTIFIER_PATTERNS=(
54
- '\b[0-9]{10,15}\b'
55
- '\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b'
56
- '\b(order|account|message|chat|user|customer)[_-]?id[[:space:]]*[:=]?[[:space:]]*[a-zA-Z0-9]+'
57
- '\b[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\b'
58
- '@[a-zA-Z0-9_]{1,15}\b'
59
- )
60
-
61
- # Personal context patterns (privacy)
62
- PERSONAL_PATTERNS=(
63
- '\bmy[[:space:]]+(wife|husband|partner|girlfriend|boyfriend|spouse)\b'
64
- '\bmy[[:space:]]+(mom|dad|mother|father|brother|sister|son|daughter|child|kid)\b'
65
- '\bmy[[:space:]]+(friend|colleague|coworker|boss|manager)[[:space:]]+[A-Za-z]+'
66
- '\b(said[[:space:]]+to[[:space:]]+me|told[[:space:]]+me|asked[[:space:]]+me|between[[:space:]]+us)\b'
67
- '\b(private|confidential|secret)[[:space:]]+(conversation|chat|message)\b'
68
- '\bin[[:space:]]+(our|my)[[:space:]]+(chat|conversation|thread|group)\b'
69
- '\b(he|she|they)[[:space:]]+(said|told|asked|mentioned)\b'
70
- )
71
-
72
- # Check if text matches any block pattern
73
- # Returns: 0 if blocked, 1 if allowed
74
- # Output: block reason if blocked
75
- check_blocks() {
76
- local text="$1"
77
- local text_lower
78
- text_lower=$(echo "$text" | tr '[:upper:]' '[:lower:]')
79
-
80
- # Check temporal patterns
81
- for pattern in "${TEMPORAL_PATTERNS[@]}"; do
82
- if echo "$text_lower" | grep -qiE "$pattern"; then
83
- echo "temporal_info"
84
- return 0
85
- fi
86
- done
87
-
88
- # Check credential patterns
89
- for pattern in "${CREDENTIAL_PATTERNS[@]}"; do
90
- if echo "$text_lower" | grep -qiE "$pattern"; then
91
- echo "credentials"
92
- return 0
93
- fi
94
- done
95
-
96
- # Check identifier patterns
97
- for pattern in "${IDENTIFIER_PATTERNS[@]}"; do
98
- if echo "$text_lower" | grep -qiE "$pattern"; then
99
- echo "identifiers"
100
- return 0
101
- fi
102
- done
103
-
104
- # Check personal patterns
105
- for pattern in "${PERSONAL_PATTERNS[@]}"; do
106
- if echo "$text_lower" | grep -qiE "$pattern"; then
107
- echo "personal_context"
108
- return 0
109
- fi
110
- done
111
-
112
- return 1
113
- }
114
-
115
- # Validate required environment variables
116
- check_env() {
117
- local missing=()
118
- [[ -z "${LANGCACHE_HOST:-}" ]] && missing+=("LANGCACHE_HOST")
119
- [[ -z "${LANGCACHE_CACHE_ID:-}" ]] && missing+=("LANGCACHE_CACHE_ID")
120
- [[ -z "${LANGCACHE_API_KEY:-}" ]] && missing+=("LANGCACHE_API_KEY")
121
-
122
- if [[ ${#missing[@]} -gt 0 ]]; then
123
- echo "Error: Missing required environment variables: ${missing[*]}" >&2
124
- echo "Set them in ~/.openclaw/secrets.env or export them directly." >&2
125
- exit 1
126
- fi
127
- }
128
-
129
- # Base URL for API calls
130
- base_url() {
131
- echo "https://${LANGCACHE_HOST}/v1/caches/${LANGCACHE_CACHE_ID}"
132
- }
133
-
134
- # Make authenticated API request
135
- api_request() {
136
- local method="$1"
137
- local endpoint="$2"
138
- local data="${3:-}"
139
-
140
- local url
141
- url="$(base_url)${endpoint}"
142
-
143
- local args=(
144
- -s
145
- -X "$method"
146
- -H "Accept: application/json"
147
- -H "Content-Type: application/json"
148
- -H "Authorization: Bearer ${LANGCACHE_API_KEY}"
149
- )
150
-
151
- if [[ -n "$data" ]]; then
152
- args+=(-d "$data")
153
- fi
154
-
155
- curl "${args[@]}" "$url"
156
- }
157
-
158
- # Parse attributes from --attr key=value arguments
159
- parse_attributes() {
160
- local attrs=()
161
- while [[ $# -gt 0 ]]; do
162
- case "$1" in
163
- --attr)
164
- shift
165
- attrs+=("$1")
166
- shift
167
- ;;
168
- *)
169
- shift
170
- ;;
171
- esac
172
- done
173
-
174
- if [[ ${#attrs[@]} -eq 0 ]]; then
175
- echo "{}"
176
- return
177
- fi
178
-
179
- local json="{"
180
- local first=true
181
- for attr in "${attrs[@]}"; do
182
- local key="${attr%%=*}"
183
- local value="${attr#*=}"
184
- if [[ "$first" == "true" ]]; then
185
- first=false
186
- else
187
- json+=","
188
- fi
189
- json+="\"${key}\":\"${value}\""
190
- done
191
- json+="}"
192
- echo "$json"
193
- }
194
-
195
- # Check command - test if text would be blocked
196
- cmd_check() {
197
- local text="$1"
198
-
199
- if [[ -z "$text" ]]; then
200
- echo "Error: Check requires text to analyze" >&2
201
- exit 1
202
- fi
203
-
204
- local block_reason
205
- if block_reason=$(check_blocks "$text"); then
206
- echo "BLOCKED: $block_reason"
207
- echo "This content will NOT be cached."
208
- return 1
209
- else
210
- echo "ALLOWED: Content can be cached"
211
- return 0
212
- fi
213
- }
214
-
215
- # Search for cached response
216
- cmd_search() {
217
- local prompt=""
218
- local threshold=""
219
- local strategy=""
220
- local attrs_args=()
221
-
222
- while [[ $# -gt 0 ]]; do
223
- case "$1" in
224
- --threshold)
225
- shift
226
- threshold="$1"
227
- shift
228
- ;;
229
- --strategy)
230
- shift
231
- strategy="$1"
232
- shift
233
- ;;
234
- --attr)
235
- attrs_args+=(--attr)
236
- shift
237
- attrs_args+=("$1")
238
- shift
239
- ;;
240
- *)
241
- if [[ -z "$prompt" ]]; then
242
- prompt="$1"
243
- fi
244
- shift
245
- ;;
246
- esac
247
- done
248
-
249
- if [[ -z "$prompt" ]]; then
250
- echo "Error: Search requires a prompt" >&2
251
- echo "Usage: langcache.sh search <prompt> [--threshold <0.0-1.0>] [--attr <key=value>]" >&2
252
- exit 1
253
- fi
254
-
255
- # Check for hard blocks before searching
256
- local block_reason
257
- if block_reason=$(check_blocks "$prompt"); then
258
- echo "{\"hit\": false, \"blocked\": true, \"reason\": \"$block_reason\"}"
259
- echo "Warning: Prompt contains blocked content ($block_reason), skipping cache" >&2
260
- return 0
261
- fi
262
-
263
- # Build JSON payload
264
- local json
265
- json=$(jq -n \
266
- --arg prompt "$prompt" \
267
- '{prompt: $prompt}')
268
-
269
- # Add similarity threshold if specified
270
- if [[ -n "$threshold" ]]; then
271
- json=$(echo "$json" | jq --arg t "$threshold" '. + {similarityThreshold: ($t | tonumber)}')
272
- fi
273
-
274
- # Add search strategies if specified
275
- if [[ -n "$strategy" ]]; then
276
- IFS=',' read -ra strategies <<< "$strategy"
277
- json=$(echo "$json" | jq --argjson s "$(printf '%s\n' "${strategies[@]}" | jq -R . | jq -s .)" '. + {searchStrategies: $s}')
278
- fi
279
-
280
- # Add attributes if specified
281
- if [[ ${#attrs_args[@]} -gt 0 ]]; then
282
- local attrs
283
- attrs=$(parse_attributes "${attrs_args[@]}")
284
- json=$(echo "$json" | jq --argjson a "$attrs" '. + {attributes: $a}')
285
- fi
286
-
287
- api_request POST "/entries/search" "$json"
288
- }
289
-
290
- # Store new cache entry
291
- cmd_store() {
292
- local prompt=""
293
- local response=""
294
- local attrs_args=()
295
- local force=false
296
-
297
- while [[ $# -gt 0 ]]; do
298
- case "$1" in
299
- --attr)
300
- attrs_args+=(--attr)
301
- shift
302
- attrs_args+=("$1")
303
- shift
304
- ;;
305
- --force)
306
- force=true
307
- shift
308
- ;;
309
- *)
310
- if [[ -z "$prompt" ]]; then
311
- prompt="$1"
312
- elif [[ -z "$response" ]]; then
313
- response="$1"
314
- fi
315
- shift
316
- ;;
317
- esac
318
- done
319
-
320
- if [[ -z "$prompt" || -z "$response" ]]; then
321
- echo "Error: Store requires prompt and response" >&2
322
- echo "Usage: langcache.sh store <prompt> <response> [--attr <key=value>]..." >&2
323
- exit 1
324
- fi
325
-
326
- # Check for hard blocks in prompt
327
- local block_reason
328
- if block_reason=$(check_blocks "$prompt"); then
329
- echo "Error: Prompt contains blocked content ($block_reason)" >&2
330
- echo "This content cannot be cached. Blocked categories:" >&2
331
- echo " - temporal_info: time-sensitive data (today, tomorrow, deadlines)" >&2
332
- echo " - credentials: API keys, passwords, tokens" >&2
333
- echo " - identifiers: emails, phone numbers, account IDs" >&2
334
- echo " - personal_context: private conversations, relationships" >&2
335
- if [[ "$force" != "true" ]]; then
336
- exit 1
337
- fi
338
- echo "Warning: --force flag used, storing anyway (not recommended)" >&2
339
- fi
340
-
341
- # Check for hard blocks in response
342
- if block_reason=$(check_blocks "$response"); then
343
- echo "Error: Response contains blocked content ($block_reason)" >&2
344
- if [[ "$force" != "true" ]]; then
345
- exit 1
346
- fi
347
- echo "Warning: --force flag used, storing anyway (not recommended)" >&2
348
- fi
349
-
350
- # Build JSON payload
351
- local json
352
- json=$(jq -n \
353
- --arg prompt "$prompt" \
354
- --arg response "$response" \
355
- '{prompt: $prompt, response: $response}')
356
-
357
- # Add attributes if specified
358
- if [[ ${#attrs_args[@]} -gt 0 ]]; then
359
- local attrs
360
- attrs=$(parse_attributes "${attrs_args[@]}")
361
- json=$(echo "$json" | jq --argjson a "$attrs" '. + {attributes: $a}')
362
- fi
363
-
364
- api_request POST "/entries" "$json"
365
- }
366
-
367
- # Delete cache entries
368
- cmd_delete() {
369
- local entry_id=""
370
- local attrs_args=()
371
-
372
- while [[ $# -gt 0 ]]; do
373
- case "$1" in
374
- --id)
375
- shift
376
- entry_id="$1"
377
- shift
378
- ;;
379
- --attr)
380
- attrs_args+=(--attr)
381
- shift
382
- attrs_args+=("$1")
383
- shift
384
- ;;
385
- *)
386
- shift
387
- ;;
388
- esac
389
- done
390
-
391
- if [[ -n "$entry_id" ]]; then
392
- # Delete by ID
393
- api_request DELETE "/entries/${entry_id}"
394
- elif [[ ${#attrs_args[@]} -gt 0 ]]; then
395
- # Delete by attributes
396
- local attrs
397
- attrs=$(parse_attributes "${attrs_args[@]}")
398
- local json
399
- json=$(jq -n --argjson a "$attrs" '{attributes: $a}')
400
- api_request DELETE "/entries" "$json"
401
- else
402
- echo "Error: Delete requires --id or --attr" >&2
403
- echo "Usage: langcache.sh delete --id <entry-id>" >&2
404
- echo " langcache.sh delete --attr <key=value>" >&2
405
- exit 1
406
- fi
407
- }
408
-
409
- # Flush entire cache
410
- cmd_flush() {
411
- echo "Warning: This will delete ALL cache entries. Proceed? (y/N)" >&2
412
- read -r confirm
413
- if [[ "$confirm" =~ ^[Yy]$ ]]; then
414
- api_request POST "/flush"
415
- echo "Cache flushed." >&2
416
- else
417
- echo "Aborted." >&2
418
- exit 1
419
- fi
420
- }
421
-
422
- # Force flush without confirmation (for scripts)
423
- cmd_flush_force() {
424
- api_request POST "/flush"
425
- }
426
-
427
- # Show usage
428
- usage() {
429
- cat <<EOF
430
- langcache.sh - CLI wrapper for Redis LangCache REST API
431
- Enforces default caching policy with hard blocks.
432
-
433
- Commands:
434
- search <prompt> Search for cached response
435
- --threshold Similarity threshold (0.0-1.0, default: uses cache default)
436
- --strategy Search strategy: exact, semantic, or both (comma-separated)
437
- --attr key=value Filter by attribute (can be repeated)
438
-
439
- store <prompt> <response> Store new cache entry
440
- --attr key=value Add attribute (can be repeated)
441
- --force Store even if blocked (not recommended)
442
-
443
- delete Delete cache entries
444
- --id <entry-id> Delete by entry ID
445
- --attr key=value Delete by attribute match
446
-
447
- check <text> Check if text would be blocked by policy
448
-
449
- flush Clear entire cache (with confirmation)
450
- flush-force Clear entire cache (no confirmation, for scripts)
451
-
452
- Caching Policy (enforced automatically):
453
- CACHEABLE:
454
- - Factual Q&A, definitions, documentation
455
- - Command explanations
456
- - Reusable reply templates (polite no, follow-up, intro)
457
- - Style transforms (warmer, shorter, firmer)
458
-
459
- BLOCKED (hard blocks):
460
- - Temporal info: today, tomorrow, deadlines, ETAs, appointments
461
- - Credentials: API keys, tokens, passwords, OTP/2FA
462
- - Identifiers: emails, phone numbers, addresses, account IDs
463
- - Personal context: relationships, private conversations
464
-
465
- Environment:
466
- LANGCACHE_HOST LangCache API host
467
- LANGCACHE_CACHE_ID Cache ID
468
- LANGCACHE_API_KEY API key
469
-
470
- Set these in ~/.openclaw/secrets.env or export directly.
471
-
472
- Examples:
473
- langcache.sh search "What is Redis?"
474
- langcache.sh search "What is Redis?" --threshold 0.9
475
- langcache.sh store "What is Redis?" "Redis is an in-memory data store..."
476
- langcache.sh store "prompt" "response" --attr model=gpt-5 --attr category=factual
477
- langcache.sh check "What's on my calendar today?" # Will show: BLOCKED: temporal_info
478
- langcache.sh delete --id abc123
479
- langcache.sh delete --attr user=123
480
- EOF
481
- }
482
-
483
- # Main
484
- main() {
485
- if [[ $# -eq 0 ]]; then
486
- usage
487
- exit 0
488
- fi
489
-
490
- local cmd="$1"
491
- shift
492
-
493
- case "$cmd" in
494
- search)
495
- check_env
496
- cmd_search "$@"
497
- ;;
498
- store)
499
- check_env
500
- cmd_store "$@"
501
- ;;
502
- delete)
503
- check_env
504
- cmd_delete "$@"
505
- ;;
506
- check)
507
- cmd_check "$@"
508
- ;;
509
- flush)
510
- check_env
511
- cmd_flush "$@"
512
- ;;
513
- flush-force)
514
- check_env
515
- cmd_flush_force "$@"
516
- ;;
517
- help|--help|-h)
518
- usage
519
- ;;
520
- *)
521
- echo "Unknown command: $cmd" >&2
522
- usage
523
- exit 1
524
- ;;
525
- esac
526
- }
527
-
528
- main "$@"