@allternit/gizzi-code 1.0.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.
Files changed (64) hide show
  1. package/WORKSPACE_INTEGRATION.md +290 -0
  2. package/chat-sessions/sessions-index.json +98 -0
  3. package/package.json +43 -0
  4. package/package.json.original +31 -0
  5. package/package.json.publish +47 -0
  6. package/src/bootstrap/state.d.ts +169 -0
  7. package/src/bootstrap/state.js +261 -0
  8. package/src/bootstrap/state.ts +371 -0
  9. package/src/context/pack-builder.d.ts +24 -0
  10. package/src/context/pack-builder.js +281 -0
  11. package/src/context/pack-builder.ts +334 -0
  12. package/src/hooks/useExitOnCtrlCD.d.ts +86 -0
  13. package/src/hooks/useExitOnCtrlCD.js +130 -0
  14. package/src/hooks/useExitOnCtrlCD.ts +202 -0
  15. package/src/hooks/useKeybinding.d.ts +89 -0
  16. package/src/hooks/useKeybinding.js +163 -0
  17. package/src/hooks/useKeybinding.ts +226 -0
  18. package/src/hooks/useTerminalSize.d.ts +41 -0
  19. package/src/hooks/useTerminalSize.js +90 -0
  20. package/src/hooks/useTerminalSize.ts +117 -0
  21. package/src/index.d.ts +23 -0
  22. package/src/index.js +24 -0
  23. package/src/index.ts +132 -0
  24. package/src/runtime/brand/brand.d.ts +488 -0
  25. package/src/runtime/brand/brand.js +464 -0
  26. package/src/runtime/brand/brand.ts +667 -0
  27. package/src/runtime/services/tokenEstimation.d.ts +187 -0
  28. package/src/runtime/services/tokenEstimation.js +423 -0
  29. package/src/runtime/services/tokenEstimation.ts +547 -0
  30. package/src/runtime/tools/Tool.d.ts +239 -0
  31. package/src/runtime/tools/Tool.js +285 -0
  32. package/src/runtime/tools/Tool.ts +524 -0
  33. package/src/scheduler/TaskScheduler.d.ts +98 -0
  34. package/src/scheduler/TaskScheduler.js +201 -0
  35. package/src/scheduler/TaskScheduler.ts +259 -0
  36. package/src/state/AppStateStore.d.ts +347 -0
  37. package/src/state/AppStateStore.js +439 -0
  38. package/src/state/AppStateStore.ts +658 -0
  39. package/src/state/WorkspaceStateStore.d.ts +43 -0
  40. package/src/state/WorkspaceStateStore.js +224 -0
  41. package/src/state/WorkspaceStateStore.ts +297 -0
  42. package/src/types/message.d.ts +190 -0
  43. package/src/types/message.js +77 -0
  44. package/src/types/message.ts +277 -0
  45. package/src/utils/format.d.ts +126 -0
  46. package/src/utils/format.js +230 -0
  47. package/src/utils/format.ts +336 -0
  48. package/src/utils/fullscreen.d.ts +161 -0
  49. package/src/utils/fullscreen.js +277 -0
  50. package/src/utils/fullscreen.ts +382 -0
  51. package/src/utils/sessionStorage.d.ts +210 -0
  52. package/src/utils/sessionStorage.js +435 -0
  53. package/src/utils/sessionStorage.ts +559 -0
  54. package/src/watcher/WorkspaceWatcher.d.ts +70 -0
  55. package/src/watcher/WorkspaceWatcher.js +191 -0
  56. package/src/watcher/WorkspaceWatcher.ts +251 -0
  57. package/src/workspace/loader.d.ts +30 -0
  58. package/src/workspace/loader.js +117 -0
  59. package/src/workspace/loader.ts +171 -0
  60. package/src/workspace/types.d.ts +91 -0
  61. package/src/workspace/types.js +7 -0
  62. package/src/workspace/types.ts +122 -0
  63. package/threads/thread_1775361175022.json +9 -0
  64. package/tsconfig.json +14 -0
@@ -0,0 +1,547 @@
1
+ /**
2
+ * @fileoverview Token estimation utilities for LLM interactions
3
+ * @module runtime/services/tokenEstimation
4
+ *
5
+ * Provides token counting and estimation functions for various LLM models.
6
+ * Supports both accurate encoding (when tiktoken is available) and
7
+ * rough estimation based on character counts.
8
+ */
9
+
10
+ import type { Message } from '../../types/message.js';
11
+
12
+ /**
13
+ * Supported LLM models for token estimation
14
+ * @typedef {'gpt-4' | 'gpt-4o' | 'gpt-4-turbo' | 'gpt-3.5-turbo' | 'claude-3-opus' | 'claude-3-sonnet' | 'claude-3-haiku' | 'claude-2' | 'gemini-pro' | 'gemini-ultra' | 'default'} SupportedModel
15
+ */
16
+ export type SupportedModel =
17
+ | 'gpt-4'
18
+ | 'gpt-4o'
19
+ | 'gpt-4-turbo'
20
+ | 'gpt-3.5-turbo'
21
+ | 'claude-3-opus'
22
+ | 'claude-3-sonnet'
23
+ | 'claude-3-haiku'
24
+ | 'claude-2'
25
+ | 'gemini-pro'
26
+ | 'gemini-ultra'
27
+ | 'default';
28
+
29
+ /**
30
+ * Token estimation configuration options
31
+ * @interface TokenEstimationOptions
32
+ */
33
+ export interface TokenEstimationOptions {
34
+ /** Target model for estimation */
35
+ readonly model?: SupportedModel;
36
+
37
+ /** Whether to include formatting overhead in estimate */
38
+ readonly includeFormatting?: boolean;
39
+
40
+ /** Custom tokens per character ratio for rough estimation */
41
+ readonly customRatio?: number;
42
+ }
43
+
44
+ /**
45
+ * Token estimation result with metadata
46
+ * @interface TokenEstimationResult
47
+ */
48
+ export interface TokenEstimationResult {
49
+ /** Estimated token count */
50
+ readonly tokens: number;
51
+
52
+ /** Method used for estimation */
53
+ readonly method: 'precise' | 'rough';
54
+
55
+ /** Model used for estimation */
56
+ readonly model: SupportedModel;
57
+
58
+ /** Confidence level (0-1) based on estimation method */
59
+ readonly confidence: number;
60
+ }
61
+
62
+ /**
63
+ * Model-specific token estimation ratios (characters per token)
64
+ * These are approximate averages and vary by language/content
65
+ * @constant {Readonly<Record<SupportedModel, number>>}
66
+ */
67
+ export const MODEL_TOKEN_RATIOS: Readonly<Record<SupportedModel, number>> = {
68
+ 'gpt-4': 3.5,
69
+ 'gpt-4o': 3.5,
70
+ 'gpt-4-turbo': 3.5,
71
+ 'gpt-3.5-turbo': 3.5,
72
+ 'claude-3-opus': 3.25,
73
+ 'claude-3-sonnet': 3.25,
74
+ 'claude-3-haiku': 3.25,
75
+ 'claude-2': 3.25,
76
+ 'gemini-pro': 3.5,
77
+ 'gemini-ultra': 3.5,
78
+ default: 3.5,
79
+ };
80
+
81
+ /**
82
+ * Message format overhead in tokens (role markers, formatting, etc.)
83
+ * @constant {number}
84
+ */
85
+ export const MESSAGE_OVERHEAD_TOKENS = 4;
86
+
87
+ /**
88
+ * Base overhead for a conversation (system prompts, etc.)
89
+ * @constant {number}
90
+ */
91
+ export const CONVERSATION_BASE_OVERHEAD = 3;
92
+
93
+ /**
94
+ * Cache for tiktoken encoder to avoid re-initialization
95
+ * @type {unknown | null}
96
+ */
97
+ let tiktokenEncoder: unknown | null = null;
98
+
99
+ /**
100
+ * Try to load tiktoken for precise token counting
101
+ * @returns {Promise<unknown | null>} Tiktoken encoder or null if not available
102
+ */
103
+ async function getTiktokenEncoder(): Promise<unknown | null> {
104
+ if (tiktokenEncoder) {
105
+ return tiktokenEncoder;
106
+ }
107
+
108
+ try {
109
+ // Dynamic import to avoid hard dependency
110
+ const tiktoken = await import('tiktoken');
111
+ // Use cl100k_base encoding (used by GPT-4 and GPT-3.5-turbo)
112
+ tiktokenEncoder = tiktoken.get_encoding('cl100k_base');
113
+ return tiktokenEncoder;
114
+ } catch {
115
+ // tiktoken not available, fall back to rough estimation
116
+ return null;
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Estimate tokens in a single text string
122
+ *
123
+ * @param {string} text - Text to estimate tokens for
124
+ * @param {TokenEstimationOptions} options - Estimation options
125
+ * @returns {TokenEstimationResult} Token estimation with metadata
126
+ *
127
+ * @example
128
+ * ```typescript
129
+ * const result = estimateTokens('Hello, world!', { model: 'gpt-4' });
130
+ * console.log(result.tokens); // ~4 tokens
131
+ * ```
132
+ */
133
+ export function estimateTokens(
134
+ text: string,
135
+ options: TokenEstimationOptions = {}
136
+ ): TokenEstimationResult {
137
+ const { model = 'default', customRatio } = options;
138
+
139
+ // Handle empty text
140
+ if (!text || text.length === 0) {
141
+ return {
142
+ tokens: 0,
143
+ method: 'precise',
144
+ model,
145
+ confidence: 1,
146
+ };
147
+ }
148
+
149
+ // Try synchronous tiktoken if available (pre-loaded)
150
+ if (tiktokenEncoder && typeof (tiktokenEncoder as { encode: (text: string) => unknown }).encode === 'function') {
151
+ try {
152
+ const tokens = (tiktokenEncoder as { encode: (text: string) => number[] }).encode(text);
153
+ return {
154
+ tokens: tokens.length,
155
+ method: 'precise',
156
+ model,
157
+ confidence: 0.95,
158
+ };
159
+ } catch {
160
+ // Fall through to rough estimation
161
+ }
162
+ }
163
+
164
+ // Rough estimation based on character count
165
+ const ratio = customRatio ?? MODEL_TOKEN_RATIOS[model] ?? MODEL_TOKEN_RATIOS.default;
166
+ const estimatedTokens = Math.ceil(text.length / ratio);
167
+
168
+ return {
169
+ tokens: estimatedTokens,
170
+ method: 'rough',
171
+ model,
172
+ confidence: 0.8,
173
+ };
174
+ }
175
+
176
+ /**
177
+ * Async version of estimateTokens that attempts to use tiktoken for precision
178
+ *
179
+ * @param {string} text - Text to estimate tokens for
180
+ * @param {TokenEstimationOptions} options - Estimation options
181
+ * @returns {Promise<TokenEstimationResult>} Token estimation with metadata
182
+ *
183
+ * @example
184
+ * ```typescript
185
+ * const result = await estimateTokensAsync('Hello, world!', { model: 'gpt-4' });
186
+ * console.log(result.method); // 'precise' if tiktoken available
187
+ * ```
188
+ */
189
+ export async function estimateTokensAsync(
190
+ text: string,
191
+ options: TokenEstimationOptions = {}
192
+ ): Promise<TokenEstimationResult> {
193
+ const { model = 'default', customRatio } = options;
194
+
195
+ // Handle empty text
196
+ if (!text || text.length === 0) {
197
+ return {
198
+ tokens: 0,
199
+ method: 'precise',
200
+ model,
201
+ confidence: 1,
202
+ };
203
+ }
204
+
205
+ // Try to use tiktoken for precise counting
206
+ const encoder = await getTiktokenEncoder();
207
+ if (encoder && typeof (encoder as { encode: (text: string) => unknown }).encode === 'function') {
208
+ try {
209
+ const tokens = (encoder as { encode: (text: string) => number[] }).encode(text);
210
+ return {
211
+ tokens: tokens.length,
212
+ method: 'precise',
213
+ model,
214
+ confidence: 0.95,
215
+ };
216
+ } catch {
217
+ // Fall through to rough estimation
218
+ }
219
+ }
220
+
221
+ // Fall back to rough estimation
222
+ return estimateTokens(text, options);
223
+ }
224
+
225
+ /**
226
+ * Estimate tokens for an array of messages
227
+ *
228
+ * @param {readonly Message[]} messages - Messages to estimate tokens for
229
+ * @param {TokenEstimationOptions} options - Estimation options
230
+ * @returns {TokenEstimationResult} Total token estimation with metadata
231
+ *
232
+ * @example
233
+ * ```typescript
234
+ * const messages = [
235
+ * { role: 'system', content: 'You are a helpful assistant.' },
236
+ * { role: 'user', content: 'Hello!' }
237
+ * ];
238
+ * const result = estimateTokensForMessages(messages);
239
+ * console.log(result.tokens); // Total estimated tokens
240
+ * ```
241
+ */
242
+ export function estimateTokensForMessages(
243
+ messages: readonly Message[],
244
+ options: TokenEstimationOptions = {}
245
+ ): TokenEstimationResult {
246
+ const { model = 'default', includeFormatting = true } = options;
247
+
248
+ if (!messages || messages.length === 0) {
249
+ return {
250
+ tokens: 0,
251
+ method: 'precise',
252
+ model,
253
+ confidence: 1,
254
+ };
255
+ }
256
+
257
+ let totalTokens = 0;
258
+ let preciseCount = 0;
259
+ let roughCount = 0;
260
+
261
+ for (const message of messages) {
262
+ // Estimate content tokens
263
+ const contentEstimate = estimateTokens(message.content, { model });
264
+ totalTokens += contentEstimate.tokens;
265
+
266
+ if (contentEstimate.method === 'precise') {
267
+ preciseCount++;
268
+ } else {
269
+ roughCount++;
270
+ }
271
+
272
+ // Add per-message overhead (formatting, role markers, etc.)
273
+ if (includeFormatting) {
274
+ totalTokens += MESSAGE_OVERHEAD_TOKENS;
275
+
276
+ // Add tokens for role name
277
+ totalTokens += estimateTokens(message.role, { model }).tokens;
278
+ }
279
+
280
+ // Add tokens for attachments if present
281
+ if (message.attachments && message.attachments.length > 0) {
282
+ for (const attachment of message.attachments) {
283
+ // Estimate based on attachment metadata
284
+ const attachmentText = `${attachment.name} ${attachment.mimeType} ${attachment.caption ?? ''}`;
285
+ totalTokens += estimateTokens(attachmentText, { model }).tokens;
286
+ }
287
+ }
288
+ }
289
+
290
+ // Add conversation base overhead
291
+ if (includeFormatting) {
292
+ totalTokens += CONVERSATION_BASE_OVERHEAD;
293
+ }
294
+
295
+ return {
296
+ tokens: totalTokens,
297
+ method: roughCount > 0 ? 'rough' : 'precise',
298
+ model,
299
+ confidence: preciseCount > roughCount ? 0.9 : 0.75,
300
+ };
301
+ }
302
+
303
+ /**
304
+ * Async version of estimateTokensForMessages with tiktoken support
305
+ *
306
+ * @param {readonly Message[]} messages - Messages to estimate tokens for
307
+ * @param {TokenEstimationOptions} options - Estimation options
308
+ * @returns {Promise<TokenEstimationResult>} Total token estimation with metadata
309
+ */
310
+ export async function estimateTokensForMessagesAsync(
311
+ messages: readonly Message[],
312
+ options: TokenEstimationOptions = {}
313
+ ): Promise<TokenEstimationResult> {
314
+ const { model = 'default', includeFormatting = true } = options;
315
+
316
+ if (!messages || messages.length === 0) {
317
+ return {
318
+ tokens: 0,
319
+ method: 'precise',
320
+ model,
321
+ confidence: 1,
322
+ };
323
+ }
324
+
325
+ // Try to initialize tiktoken
326
+ const encoder = await getTiktokenEncoder();
327
+ const usePrecise = encoder !== null;
328
+
329
+ let totalTokens = 0;
330
+
331
+ for (const message of messages) {
332
+ // Use precise or rough estimation based on availability
333
+ if (usePrecise) {
334
+ const contentResult = await estimateTokensAsync(message.content, { model });
335
+ totalTokens += contentResult.tokens;
336
+ } else {
337
+ const contentResult = estimateTokens(message.content, { model });
338
+ totalTokens += contentResult.tokens;
339
+ }
340
+
341
+ // Add per-message overhead
342
+ if (includeFormatting) {
343
+ totalTokens += MESSAGE_OVERHEAD_TOKENS;
344
+ const roleResult = usePrecise
345
+ ? await estimateTokensAsync(message.role, { model })
346
+ : estimateTokens(message.role, { model });
347
+ totalTokens += roleResult.tokens;
348
+ }
349
+
350
+ // Add attachment estimates
351
+ if (message.attachments && message.attachments.length > 0) {
352
+ for (const attachment of message.attachments) {
353
+ const attachmentText = `${attachment.name} ${attachment.mimeType} ${attachment.caption ?? ''}`;
354
+ const attachResult = usePrecise
355
+ ? await estimateTokensAsync(attachmentText, { model })
356
+ : estimateTokens(attachmentText, { model });
357
+ totalTokens += attachResult.tokens;
358
+ }
359
+ }
360
+ }
361
+
362
+ if (includeFormatting) {
363
+ totalTokens += CONVERSATION_BASE_OVERHEAD;
364
+ }
365
+
366
+ return {
367
+ tokens: totalTokens,
368
+ method: usePrecise ? 'precise' : 'rough',
369
+ model,
370
+ confidence: usePrecise ? 0.95 : 0.75,
371
+ };
372
+ }
373
+
374
+ /**
375
+ * Rough token count estimation without any dependencies
376
+ * Uses a simple characters-per-token heuristic
377
+ *
378
+ * @param {string | readonly Message[]} input - Text or messages to estimate
379
+ * @param {SupportedModel} model - Model for estimation ratios
380
+ * @returns {number} Estimated token count
381
+ *
382
+ * @example
383
+ * ```typescript
384
+ * // Estimate text
385
+ * const textTokens = roughTokenCountEstimation('Hello, world!');
386
+ *
387
+ * // Estimate messages
388
+ * const messageTokens = roughTokenCountEstimation([
389
+ * { role: 'user', content: 'Hello!' }
390
+ * ]);
391
+ * ```
392
+ */
393
+ export function roughTokenCountEstimation(
394
+ input: string | readonly Message[],
395
+ model: SupportedModel = 'default'
396
+ ): number {
397
+ const ratio = MODEL_TOKEN_RATIOS[model] ?? MODEL_TOKEN_RATIOS.default;
398
+
399
+ // Handle string input
400
+ if (typeof input === 'string') {
401
+ if (!input) return 0;
402
+ return Math.ceil(input.length / ratio);
403
+ }
404
+
405
+ // Handle message array input
406
+ if (!Array.isArray(input) || input.length === 0) {
407
+ return 0;
408
+ }
409
+
410
+ let totalChars = 0;
411
+
412
+ for (const message of input) {
413
+ // Add content length
414
+ if (message.content) {
415
+ totalChars += message.content.length;
416
+ }
417
+
418
+ // Add role length
419
+ if (message.role) {
420
+ totalChars += message.role.length;
421
+ }
422
+
423
+ // Add overhead
424
+ totalChars += MESSAGE_OVERHEAD_TOKENS * ratio;
425
+
426
+ // Add attachment metadata
427
+ if (message.attachments) {
428
+ for (const attachment of message.attachments) {
429
+ totalChars += (attachment.name?.length ?? 0);
430
+ totalChars += (attachment.mimeType?.length ?? 0);
431
+ totalChars += (attachment.caption?.length ?? 0);
432
+ }
433
+ }
434
+ }
435
+
436
+ // Add conversation overhead
437
+ totalChars += CONVERSATION_BASE_OVERHEAD * ratio;
438
+
439
+ return Math.ceil(totalChars / ratio);
440
+ }
441
+
442
+ /**
443
+ * Check if text would exceed token limit
444
+ *
445
+ * @param {string} text - Text to check
446
+ * @param {number} limit - Token limit
447
+ * @param {TokenEstimationOptions} options - Estimation options
448
+ * @returns {{ withinLimit: true } | { withinLimit: false; exceededBy: number }} Limit check result
449
+ *
450
+ * @example
451
+ * ```typescript
452
+ * const result = checkTokenLimit('Long text...', 100);
453
+ * if (!result.withinLimit) {
454
+ * console.log(`Exceeded by ${result.exceededBy} tokens`);
455
+ * }
456
+ * ```
457
+ */
458
+ export function checkTokenLimit(
459
+ text: string,
460
+ limit: number,
461
+ options: TokenEstimationOptions = {}
462
+ ): { withinLimit: true } | { withinLimit: false; exceededBy: number } {
463
+ const estimate = estimateTokens(text, options);
464
+
465
+ if (estimate.tokens <= limit) {
466
+ return { withinLimit: true };
467
+ }
468
+
469
+ return {
470
+ withinLimit: false,
471
+ exceededBy: estimate.tokens - limit,
472
+ };
473
+ }
474
+
475
+ /**
476
+ * Truncate text to fit within token limit
477
+ *
478
+ * @param {string} text - Text to truncate
479
+ * @param {number} maxTokens - Maximum token count
480
+ * @param {TokenEstimationOptions} options - Estimation options
481
+ * @returns {string} Truncated text
482
+ *
483
+ * @example
484
+ * ```typescript
485
+ * const truncated = truncateToTokenLimit(longText, 100);
486
+ * ```
487
+ */
488
+ export function truncateToTokenLimit(
489
+ text: string,
490
+ maxTokens: number,
491
+ options: TokenEstimationOptions = {}
492
+ ): string {
493
+ const estimate = estimateTokens(text, options);
494
+
495
+ if (estimate.tokens <= maxTokens) {
496
+ return text;
497
+ }
498
+
499
+ const ratio = options.customRatio ??
500
+ MODEL_TOKEN_RATIOS[options.model ?? 'default'] ??
501
+ MODEL_TOKEN_RATIOS.default;
502
+
503
+ // Calculate approximate character limit
504
+ const targetChars = Math.floor(maxTokens * ratio);
505
+
506
+ // Simple truncation (could be improved to respect word boundaries)
507
+ return text.substring(0, targetChars) + '...';
508
+ }
509
+
510
+ /**
511
+ * Get model-specific context window size
512
+ *
513
+ * @param {SupportedModel} model - Model name
514
+ * @returns {number} Context window size in tokens
515
+ */
516
+ export function getModelContextWindow(model: SupportedModel): number {
517
+ const contextWindows: Readonly<Record<SupportedModel, number>> = {
518
+ 'gpt-4': 8192,
519
+ 'gpt-4o': 128000,
520
+ 'gpt-4-turbo': 128000,
521
+ 'gpt-3.5-turbo': 16385,
522
+ 'claude-3-opus': 200000,
523
+ 'claude-3-sonnet': 200000,
524
+ 'claude-3-haiku': 200000,
525
+ 'claude-2': 100000,
526
+ 'gemini-pro': 1000000,
527
+ 'gemini-ultra': 1000000,
528
+ default: 4096,
529
+ };
530
+
531
+ return contextWindows[model] ?? contextWindows.default;
532
+ }
533
+
534
+ // Default export
535
+ export default {
536
+ estimateTokens,
537
+ estimateTokensAsync,
538
+ estimateTokensForMessages,
539
+ estimateTokensForMessagesAsync,
540
+ roughTokenCountEstimation,
541
+ checkTokenLimit,
542
+ truncateToTokenLimit,
543
+ getModelContextWindow,
544
+ MODEL_TOKEN_RATIOS,
545
+ MESSAGE_OVERHEAD_TOKENS,
546
+ CONVERSATION_BASE_OVERHEAD,
547
+ };