@autumnsgrove/groveengine 0.9.85 → 0.9.91

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 (109) hide show
  1. package/dist/components/admin/GutterManager.svelte +0 -1
  2. package/dist/components/admin/MarkdownEditor.svelte +5 -2
  3. package/dist/components/custom/TableOfContents.svelte +24 -6
  4. package/dist/config/wisp.d.ts +4 -42
  5. package/dist/config/wisp.js +2 -77
  6. package/dist/curios/timeline/Timeline.svelte +35 -17
  7. package/dist/curios/timeline/index.d.ts +1 -1
  8. package/dist/curios/timeline/index.js +1 -1
  9. package/dist/curios/timeline/providers/openrouter.d.ts +0 -8
  10. package/dist/curios/timeline/providers/openrouter.js +1 -72
  11. package/dist/durable-objects/TenantDO.d.ts +7 -106
  12. package/dist/durable-objects/TenantDO.js +7 -423
  13. package/dist/durable-objects/index.d.ts +3 -3
  14. package/dist/durable-objects/index.js +3 -4
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.js +3 -3
  17. package/dist/lumen/client.d.ts +113 -0
  18. package/dist/lumen/client.js +358 -0
  19. package/dist/lumen/config.d.ts +113 -0
  20. package/dist/lumen/config.js +269 -0
  21. package/dist/lumen/errors.d.ts +50 -0
  22. package/dist/lumen/errors.js +92 -0
  23. package/dist/lumen/index.d.ts +68 -0
  24. package/dist/lumen/index.js +97 -0
  25. package/dist/lumen/mcp.d.ts +75 -0
  26. package/dist/lumen/mcp.js +92 -0
  27. package/dist/lumen/pipeline/postprocessor.d.ts +106 -0
  28. package/dist/lumen/pipeline/postprocessor.js +108 -0
  29. package/dist/lumen/pipeline/preprocessor.d.ts +62 -0
  30. package/dist/lumen/pipeline/preprocessor.js +208 -0
  31. package/dist/lumen/providers/cloudflare-ai.d.ts +39 -0
  32. package/dist/lumen/providers/cloudflare-ai.js +202 -0
  33. package/dist/lumen/providers/index.d.ts +28 -0
  34. package/dist/lumen/providers/index.js +46 -0
  35. package/dist/lumen/providers/openrouter.d.ts +56 -0
  36. package/dist/lumen/providers/openrouter.js +389 -0
  37. package/dist/lumen/providers/types.d.ts +90 -0
  38. package/dist/lumen/providers/types.js +7 -0
  39. package/dist/lumen/quota/limits.d.ts +50 -0
  40. package/dist/lumen/quota/limits.js +127 -0
  41. package/dist/lumen/quota/tracker.d.ts +94 -0
  42. package/dist/lumen/quota/tracker.js +216 -0
  43. package/dist/lumen/router.d.ts +65 -0
  44. package/dist/lumen/router.js +238 -0
  45. package/dist/lumen/shutter.d.ts +39 -0
  46. package/dist/lumen/shutter.js +42 -0
  47. package/dist/lumen/songbird.d.ts +27 -0
  48. package/dist/lumen/songbird.js +178 -0
  49. package/dist/lumen/types.d.ts +340 -0
  50. package/dist/lumen/types.js +9 -0
  51. package/dist/server/encryption.js +3 -3
  52. package/dist/server/env-validation.d.ts +2 -6
  53. package/dist/server/env-validation.js +1 -3
  54. package/dist/server/inference-client.d.ts +3 -46
  55. package/dist/server/inference-client.js +3 -135
  56. package/dist/server/petal/lumen-classify.d.ts +26 -0
  57. package/dist/server/petal/lumen-classify.js +144 -0
  58. package/dist/server/services/__mocks__/cloudflare.js +158 -57
  59. package/dist/server/services/index.d.ts +2 -0
  60. package/dist/server/services/index.js +9 -0
  61. package/dist/server/services/og-fetcher.d.ts +60 -0
  62. package/dist/server/services/og-fetcher.js +415 -0
  63. package/dist/thorn/config.d.ts +31 -0
  64. package/dist/thorn/config.js +149 -0
  65. package/dist/thorn/index.d.ts +44 -0
  66. package/dist/thorn/index.js +45 -0
  67. package/dist/thorn/moderate.d.ts +35 -0
  68. package/dist/thorn/moderate.js +54 -0
  69. package/dist/thorn/types.d.ts +107 -0
  70. package/dist/thorn/types.js +9 -0
  71. package/dist/types/og.d.ts +65 -0
  72. package/dist/types/og.js +9 -0
  73. package/dist/ui/components/chrome/defaults.js +2 -2
  74. package/dist/ui/components/content/LinkPreview.svelte +353 -0
  75. package/dist/ui/components/content/LinkPreview.svelte.d.ts +63 -0
  76. package/dist/ui/components/content/index.d.ts +2 -1
  77. package/dist/ui/components/content/index.js +4 -3
  78. package/dist/ui/vineyard/AuthButton.svelte +127 -0
  79. package/dist/ui/vineyard/AuthButton.svelte.d.ts +6 -0
  80. package/dist/ui/vineyard/CodeExample.svelte +186 -0
  81. package/dist/ui/vineyard/CodeExample.svelte.d.ts +8 -0
  82. package/dist/ui/vineyard/DemoContainer.svelte +139 -0
  83. package/dist/ui/vineyard/DemoContainer.svelte.d.ts +8 -0
  84. package/dist/ui/vineyard/FeatureCard.svelte +127 -0
  85. package/dist/ui/vineyard/FeatureCard.svelte.d.ts +8 -0
  86. package/dist/ui/vineyard/RoadmapSection.svelte +238 -0
  87. package/dist/ui/vineyard/RoadmapSection.svelte.d.ts +4 -0
  88. package/dist/ui/vineyard/StatusBadge.svelte +88 -0
  89. package/dist/ui/vineyard/StatusBadge.svelte.d.ts +4 -0
  90. package/dist/ui/vineyard/TierGate.svelte +172 -0
  91. package/dist/ui/vineyard/TierGate.svelte.d.ts +10 -0
  92. package/dist/ui/vineyard/UserMenu.svelte +289 -0
  93. package/dist/ui/vineyard/UserMenu.svelte.d.ts +6 -0
  94. package/dist/ui/vineyard/VineyardLayout.svelte +254 -0
  95. package/dist/ui/vineyard/VineyardLayout.svelte.d.ts +8 -0
  96. package/dist/ui/vineyard/auth.d.ts +81 -0
  97. package/dist/ui/vineyard/auth.js +134 -0
  98. package/dist/ui/vineyard/index.d.ts +15 -6
  99. package/dist/ui/vineyard/index.js +21 -7
  100. package/dist/ui/vineyard/types.d.ts +147 -0
  101. package/dist/ui/vineyard/types.js +5 -0
  102. package/dist/utils/markdown.d.ts +6 -0
  103. package/dist/utils/markdown.js +50 -44
  104. package/package.json +12 -3
  105. package/static/apple-touch-icon.png +0 -0
  106. package/static/favicon-32x32.png +0 -0
  107. package/static/favicon.svg +27 -16
  108. package/static/icon-192.png +0 -0
  109. package/static/icon-512.png +0 -0
@@ -1,5 +1,4 @@
1
1
  <script>
2
- import { marked } from "marked";
3
2
  import { Input, Button } from '../../ui';
4
3
  import Dialog from "../../ui/components/ui/Dialog.svelte";
5
4
  import Select from "../../ui/components/ui/Select.svelte";
@@ -1,7 +1,10 @@
1
1
  <script>
2
- import { marked } from "marked";
2
+ import MarkdownIt from "markdown-it";
3
3
  import { onMount, tick } from "svelte";
4
4
  import { sanitizeMarkdown } from "../../utils/sanitize";
5
+
6
+ // Local instance for admin editor preview
7
+ const editorMd = new MarkdownIt({ html: false, linkify: true });
5
8
  import { extractHeaders } from "../../utils/markdown";
6
9
  import "../../styles/content.css";
7
10
  import { Button, Input, Logo } from '../../ui';
@@ -111,7 +114,7 @@
111
114
  let wordCount = $derived(content.trim() ? content.trim().split(/\s+/).length : 0);
112
115
  let charCount = $derived(content.length);
113
116
  let lineCount = $derived(content.split("\n").length);
114
- let previewHtml = $derived(content ? sanitizeMarkdown(/** @type {string} */ (marked.parse(content, { async: false }))) : "");
117
+ let previewHtml = $derived(content ? sanitizeMarkdown(editorMd.render(content)) : "");
115
118
  let previewHeaders = $derived(content ? extractHeaders(content) : []);
116
119
 
117
120
  let readingTime = $derived.by(() => {
@@ -102,8 +102,9 @@
102
102
  <style>
103
103
  .toc {
104
104
  position: sticky;
105
- top: 2rem;
106
- max-height: calc(100vh - 4rem);
105
+ /* Account for sticky navbar height + divider line + breathing room */
106
+ top: 6.5rem;
107
+ max-height: calc(100vh - 7.5rem);
107
108
  overflow-y: auto;
108
109
  padding: 1.25rem;
109
110
  font-size: 0.875rem;
@@ -214,15 +215,32 @@
214
215
  .level-6 .toc-link {
215
216
  padding-left: 4rem;
216
217
  }
217
- /* Scrollbar styling */
218
+ /* Scrollbar styling - hidden by default, visible on hover */
219
+ .toc {
220
+ /* Firefox scrollbar hiding */
221
+ scrollbar-width: thin;
222
+ scrollbar-color: transparent transparent;
223
+ }
224
+ .toc:hover {
225
+ scrollbar-color: var(--color-foreground-subtle, rgba(0, 0, 0, 0.2)) transparent;
226
+ }
227
+ /* Webkit (Chrome, Safari, Edge) scrollbar styling */
218
228
  .toc::-webkit-scrollbar {
219
- width: 4px;
229
+ width: 6px;
220
230
  }
221
231
  .toc::-webkit-scrollbar-track {
222
232
  background: transparent;
223
233
  }
224
234
  .toc::-webkit-scrollbar-thumb {
225
- background: var(--color-foreground-subtle, #ccc);
226
- border-radius: 2px;
235
+ background: transparent;
236
+ border-radius: 3px;
237
+ -webkit-transition: background 0.2s ease;
238
+ transition: background 0.2s ease;
239
+ }
240
+ .toc:hover::-webkit-scrollbar-thumb {
241
+ background: var(--color-foreground-subtle, rgba(0, 0, 0, 0.2));
242
+ }
243
+ :global(.dark) .toc:hover::-webkit-scrollbar-thumb {
244
+ background: rgba(255, 255, 255, 0.15);
227
245
  }
228
246
  </style>
@@ -1,37 +1,11 @@
1
1
  /**
2
2
  * Wisp - Grove Writing Assistant Configuration
3
3
  *
4
- * Model configuration, provider settings, and pricing for the Wisp writing assistant.
5
- * Uses privacy-first providers with Zero Data Retention (ZDR).
4
+ * Rate limits, content constraints, and prompt modes for the Wisp writing assistant.
5
+ * AI inference is now handled by Lumen (see $lib/lumen).
6
6
  *
7
7
  * @see docs/specs/writing-assistant-unified-spec.md
8
8
  */
9
- export interface ProviderConfig {
10
- name: string;
11
- baseUrl: string;
12
- role: 'primary' | 'backup' | 'tertiary';
13
- zdr: boolean;
14
- models: Record<string, string>;
15
- }
16
- /**
17
- * Approved inference providers with ZDR support
18
- * Order determines fallback priority
19
- */
20
- export declare const PROVIDERS: Record<string, ProviderConfig>;
21
- export interface ModelFallback {
22
- provider: string;
23
- model: string;
24
- }
25
- /**
26
- * Model fallback cascade
27
- * Try in order until one succeeds
28
- */
29
- export declare const MODEL_FALLBACK_CASCADE: ModelFallback[];
30
- export interface ModelPricing {
31
- input: number;
32
- output: number;
33
- }
34
- export declare const MODEL_PRICING: Record<string, ModelPricing>;
35
9
  /** Maximum content length for analysis (characters) */
36
10
  export declare const MAX_CONTENT_LENGTH = 50000;
37
11
  /** Max output tokens by analysis type and mode */
@@ -61,8 +35,8 @@ export declare const COST_CAP: {
61
35
  readonly maxCostUSD: 5;
62
36
  readonly warningThreshold: 0.8;
63
37
  };
64
- export type AnalysisMode = 'quick' | 'thorough';
65
- export type AnalysisAction = 'grammar' | 'tone' | 'readability';
38
+ export type AnalysisMode = "quick" | "thorough";
39
+ export type AnalysisAction = "grammar" | "tone" | "readability";
66
40
  /**
67
41
  * Prompt modes control analysis depth without changing models
68
42
  */
@@ -80,18 +54,6 @@ export declare const PROMPT_MODES: {
80
54
  readonly maxOutputMultiplier: 2;
81
55
  };
82
56
  };
83
- /**
84
- * Calculate cost for token usage
85
- */
86
- export declare function calculateCost(model: string, inputTokens: number, outputTokens: number): number;
87
- /**
88
- * Get the model ID for a provider
89
- */
90
- export declare function getModelId(provider: string, model: string): string | null;
91
- /**
92
- * Get provider configuration
93
- */
94
- export declare function getProvider(provider: string): ProviderConfig | null;
95
57
  /**
96
58
  * Get max tokens for an action and mode
97
59
  */
@@ -1,65 +1,11 @@
1
1
  /**
2
2
  * Wisp - Grove Writing Assistant Configuration
3
3
  *
4
- * Model configuration, provider settings, and pricing for the Wisp writing assistant.
5
- * Uses privacy-first providers with Zero Data Retention (ZDR).
4
+ * Rate limits, content constraints, and prompt modes for the Wisp writing assistant.
5
+ * AI inference is now handled by Lumen (see $lib/lumen).
6
6
  *
7
7
  * @see docs/specs/writing-assistant-unified-spec.md
8
8
  */
9
- /**
10
- * Approved inference providers with ZDR support
11
- * Order determines fallback priority
12
- */
13
- export const PROVIDERS = {
14
- fireworks: {
15
- name: "Fireworks AI",
16
- baseUrl: "https://api.fireworks.ai/inference/v1",
17
- role: "primary",
18
- zdr: true, // Zero Data Retention default for open models
19
- models: {
20
- "deepseek-v3.2": "accounts/fireworks/models/deepseek-v3p2",
21
- "kimi-k2": "accounts/fireworks/models/kimi-k2-instruct-0905",
22
- "llama-3.1-70b": "accounts/fireworks/models/llama-v3p1-70b-instruct",
23
- },
24
- },
25
- cerebras: {
26
- name: "Cerebras",
27
- baseUrl: "https://api.cerebras.ai/v1",
28
- role: "backup",
29
- zdr: true, // US-based, zero retention
30
- models: {
31
- "llama-3.3-70b": "llama-3.3-70b",
32
- "gpt-oss-120b": "gpt-oss-120b",
33
- },
34
- },
35
- groq: {
36
- name: "Groq",
37
- baseUrl: "https://api.groq.com/openai/v1",
38
- role: "tertiary",
39
- zdr: true, // Explicit ZDR toggle
40
- models: {
41
- "llama-3.3-70b": "llama-3.3-70b-versatile",
42
- },
43
- },
44
- };
45
- /**
46
- * Model fallback cascade
47
- * Try in order until one succeeds
48
- */
49
- export const MODEL_FALLBACK_CASCADE = [
50
- { provider: "fireworks", model: "deepseek-v3.2" },
51
- { provider: "fireworks", model: "kimi-k2" },
52
- { provider: "fireworks", model: "llama-3.1-70b" },
53
- { provider: "cerebras", model: "llama-3.3-70b" },
54
- { provider: "groq", model: "llama-3.3-70b" },
55
- ];
56
- export const MODEL_PRICING = {
57
- "deepseek-v3.2": { input: 0.56, output: 1.68 },
58
- "kimi-k2": { input: 0.6, output: 2.5 },
59
- "llama-3.1-70b": { input: 0.9, output: 0.9 },
60
- "llama-3.3-70b": { input: 0.59, output: 0.79 },
61
- "gpt-oss-120b": { input: 0.25, output: 0.69 },
62
- };
63
9
  // ============================================================================
64
10
  // Limits & Thresholds
65
11
  // ============================================================================
@@ -106,27 +52,6 @@ export const PROMPT_MODES = {
106
52
  // ============================================================================
107
53
  // Helper Functions
108
54
  // ============================================================================
109
- /**
110
- * Calculate cost for token usage
111
- */
112
- export function calculateCost(model, inputTokens, outputTokens) {
113
- const pricing = MODEL_PRICING[model] || MODEL_PRICING["deepseek-v3.2"];
114
- const cost = (inputTokens * pricing.input + outputTokens * pricing.output) / 1_000_000;
115
- // Round to 6 decimal places to avoid floating point precision issues
116
- return Math.round(cost * 1_000_000) / 1_000_000;
117
- }
118
- /**
119
- * Get the model ID for a provider
120
- */
121
- export function getModelId(provider, model) {
122
- return PROVIDERS[provider]?.models[model] || null;
123
- }
124
- /**
125
- * Get provider configuration
126
- */
127
- export function getProvider(provider) {
128
- return PROVIDERS[provider] || null;
129
- }
130
55
  /**
131
56
  * Get max tokens for an action and mode
132
57
  */
@@ -10,8 +10,11 @@
10
10
  * - Pagination with "Load More"
11
11
  */
12
12
 
13
- import { marked } from 'marked';
13
+ import MarkdownIt from 'markdown-it';
14
14
  import { sanitizeMarkdown } from '../../utils';
15
+
16
+ // Local instance with breaks: true for timeline rendering
17
+ const timelineMd = new MarkdownIt({ breaks: true, linkify: true });
15
18
  import {
16
19
  Calendar,
17
20
  GitCommit,
@@ -76,12 +79,6 @@
76
79
  let loadingMore = $state(false);
77
80
  let expandedCards = $state(new Set<string>());
78
81
 
79
- // Configure marked for safe rendering
80
- $effect(() => {
81
- marked.setOptions({
82
- breaks: true
83
- });
84
- });
85
82
 
86
83
  // Fun rest day messages
87
84
  const REST_DAY_MESSAGES = [
@@ -168,7 +165,7 @@
168
165
  let withRepoLinks = text;
169
166
  if (githubUsername) {
170
167
  withRepoLinks = text.replace(
171
- /^### (.+)$/gm,
168
+ /^### (?!\[)(.+)$/gm,
172
169
  (match, repoName) => {
173
170
  const cleanName = repoName.trim();
174
171
  return `### [${cleanName}](https://github.com/${githubUsername}/${cleanName})`;
@@ -177,7 +174,7 @@
177
174
  }
178
175
 
179
176
  // Parse to HTML and sanitize
180
- let html = sanitizeMarkdown(marked.parse(withRepoLinks) as string);
177
+ let html = sanitizeMarkdown(timelineMd.render(withRepoLinks));
181
178
 
182
179
  // Inject gutter comments after headers
183
180
  for (const [headerName, items] of Object.entries(gutterByHeader)) {
@@ -310,12 +307,14 @@
310
307
  <FolderGit2 size={14} />
311
308
  {summary.repos_active?.join(', ') || 'Unknown'}
312
309
  </span>
313
- <span class="changes">
314
- <Plus size={14} class="plus-icon" />
315
- {summary.total_additions.toLocaleString()}
316
- <Minus size={14} class="minus-icon" />
317
- {summary.total_deletions.toLocaleString()}
318
- </span>
310
+ {#if summary.total_additions > 0 || summary.total_deletions > 0}
311
+ <span class="changes">
312
+ <Plus size={14} class="plus-icon" />
313
+ {summary.total_additions.toLocaleString()}
314
+ <Minus size={14} class="minus-icon" />
315
+ {summary.total_deletions.toLocaleString()}
316
+ </span>
317
+ {/if}
319
318
  </div>
320
319
 
321
320
  {#if summary.detailed_timeline && isExpanded}
@@ -442,11 +441,17 @@
442
441
  font-weight: 600;
443
442
  color: var(--color-foreground, #333);
444
443
  }
444
+ :global(.dark) .date-full {
445
+ color: var(--bark, #f5f2ea);
446
+ }
445
447
  .date-short {
446
448
  display: none;
447
449
  font-weight: 600;
448
450
  color: var(--color-foreground, #333);
449
451
  }
452
+ :global(.dark) .date-short {
453
+ color: var(--bark, #f5f2ea);
454
+ }
450
455
  :global(.today-badge) {
451
456
  background: var(--grove-500, #4ade80);
452
457
  color: white;
@@ -519,13 +524,16 @@
519
524
  font-size: 0.95rem;
520
525
  }
521
526
  :global(.dark) .rest-message {
522
- color: #777;
527
+ color: var(--bark-600, #b69575);
523
528
  }
524
529
  .brief-summary {
525
530
  margin: 0 0 0.75rem;
526
531
  color: var(--color-foreground, #333);
527
532
  line-height: 1.5;
528
533
  }
534
+ :global(.dark) .brief-summary {
535
+ color: var(--bark, #f5f2ea);
536
+ }
529
537
  .meta-info {
530
538
  display: flex;
531
539
  flex-wrap: wrap;
@@ -534,6 +542,9 @@
534
542
  color: var(--color-foreground, #333);
535
543
  margin-bottom: 0.75rem;
536
544
  }
545
+ :global(.dark) .meta-info {
546
+ color: var(--bark-700, #ccb59c);
547
+ }
537
548
  .repos, .changes {
538
549
  display: flex;
539
550
  align-items: center;
@@ -607,6 +618,7 @@
607
618
  }
608
619
  :global(.dark) .detailed-timeline {
609
620
  background: var(--cream-300, #2a2a2a);
621
+ color: var(--bark, #f5f2ea);
610
622
  }
611
623
  .markdown-content :global(h2) {
612
624
  font-size: 1.1rem;
@@ -625,6 +637,9 @@
625
637
  margin: 1rem 0 0.5rem;
626
638
  font-weight: 600;
627
639
  }
640
+ :global(.dark) .markdown-content :global(h3) {
641
+ color: var(--bark, #f5f2ea);
642
+ }
628
643
  .markdown-content :global(h3 a) {
629
644
  color: #2c5f2d;
630
645
  text-decoration: none;
@@ -647,6 +662,9 @@
647
662
  margin-bottom: 0.25rem;
648
663
  color: var(--color-foreground, #333);
649
664
  }
665
+ :global(.dark) .markdown-content :global(li) {
666
+ color: var(--bark, #f5f2ea);
667
+ }
650
668
  .markdown-content :global(code) {
651
669
  background: var(--color-border-strong, #e0e0e0);
652
670
  padding: 0.15rem 0.35rem;
@@ -681,7 +699,7 @@
681
699
  }
682
700
  :global(.dark) .timeline-footer {
683
701
  border-top-color: var(--color-border-strong, #444);
684
- color: #666;
702
+ color: var(--bark-700, #ccb59c);
685
703
  }
686
704
 
687
705
  /* Mobile Responsiveness */
@@ -15,7 +15,7 @@ import { type GutterComment as _GutterComment } from "./voices";
15
15
  import type { ContextBrief as _ContextBrief, DetectedFocus as _DetectedFocus } from "./context";
16
16
  export { default as Timeline } from "./Timeline.svelte";
17
17
  export { default as Heatmap } from "./Heatmap.svelte";
18
- export { callOpenRouter, calculateOpenRouterCost, getOpenRouterModels, validateOpenRouterKey, OPENROUTER_MODELS, DEFAULT_OPENROUTER_MODEL, type OpenRouterModel, type OpenRouterResponse, type OpenRouterOptions, type OpenRouterKeyValidation, } from "./providers/openrouter";
18
+ export { getOpenRouterModels, validateOpenRouterKey, OPENROUTER_MODELS, DEFAULT_OPENROUTER_MODEL, type OpenRouterModel, type OpenRouterResponse, type OpenRouterOptions, type OpenRouterKeyValidation, } from "./providers/openrouter";
19
19
  export { buildVoicedPrompt, getAllVoices, getVoice, buildCustomVoice, VOICE_PRESETS, DEFAULT_VOICE, professional, quest, casual, poetic, minimal, type VoicePreset, type VoicePromptResult, type CustomVoiceConfig, type Commit, type GutterComment, type PromptContextInput, } from "./voices";
20
20
  export { detectTaskFromText, detectTask, generateContextBrief, buildDetectedFocus, buildSummaryContextData, getHistoricalContext, detectContinuation, formatHistoricalContextForPrompt, formatContinuationForPrompt, buildPromptContext, type TaskType, type ContextBrief, type DetectedFocus, type TaskContinuation, type HistoricalContextEntry, type SummaryContextData, type PromptContext, } from "./context";
21
21
  /**
@@ -24,7 +24,7 @@ export { default as Heatmap } from "./Heatmap.svelte";
24
24
  // =============================================================================
25
25
  // OpenRouter Provider
26
26
  // =============================================================================
27
- export { callOpenRouter, calculateOpenRouterCost, getOpenRouterModels, validateOpenRouterKey, OPENROUTER_MODELS, DEFAULT_OPENROUTER_MODEL, } from "./providers/openrouter";
27
+ export { getOpenRouterModels, validateOpenRouterKey, OPENROUTER_MODELS, DEFAULT_OPENROUTER_MODEL, } from "./providers/openrouter";
28
28
  // =============================================================================
29
29
  // Voice Presets
30
30
  // =============================================================================
@@ -38,14 +38,6 @@ export interface OpenRouterKeyValidation {
38
38
  */
39
39
  export declare const OPENROUTER_MODELS: Record<string, OpenRouterModel>;
40
40
  export declare const DEFAULT_OPENROUTER_MODEL = "deepseek/deepseek-v3.2";
41
- /**
42
- * Call OpenRouter API
43
- */
44
- export declare function callOpenRouter(apiKey: string, model: string, systemPrompt: string, userPrompt: string, options?: OpenRouterOptions): Promise<OpenRouterResponse>;
45
- /**
46
- * Calculate cost for OpenRouter request
47
- */
48
- export declare function calculateOpenRouterCost(model: string, inputTokens: number, outputTokens: number): number;
49
41
  /**
50
42
  * Get list of recommended OpenRouter models for the UI
51
43
  */
@@ -89,79 +89,8 @@ export const OPENROUTER_MODELS = {
89
89
  };
90
90
  export const DEFAULT_OPENROUTER_MODEL = "deepseek/deepseek-v3.2";
91
91
  // =============================================================================
92
- // Utilities
92
+ // UI Utilities (kept for model picker and key validation)
93
93
  // =============================================================================
94
- /**
95
- * Rough token estimation (4 chars ≈ 1 token)
96
- */
97
- function estimateTokens(text) {
98
- if (!text)
99
- return 0;
100
- return Math.ceil(text.length / 4);
101
- }
102
- // =============================================================================
103
- // API Implementation
104
- // =============================================================================
105
- /**
106
- * Call OpenRouter API
107
- */
108
- export async function callOpenRouter(apiKey, model, systemPrompt, userPrompt, options = {}) {
109
- const { maxTokens = 2048, temperature = 0.5, siteUrl = "https://grove.place", siteName = "Grove Timeline Curio", } = options;
110
- const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
111
- method: "POST",
112
- headers: {
113
- "Content-Type": "application/json",
114
- Authorization: `Bearer ${apiKey}`,
115
- "HTTP-Referer": siteUrl,
116
- "X-Title": siteName,
117
- },
118
- body: JSON.stringify({
119
- model: model,
120
- messages: [
121
- { role: "system", content: systemPrompt },
122
- { role: "user", content: userPrompt },
123
- ],
124
- max_tokens: maxTokens,
125
- temperature: temperature,
126
- }),
127
- });
128
- if (!response.ok) {
129
- const errorText = await response.text();
130
- let errorMessage = `OpenRouter API error (${response.status})`;
131
- try {
132
- const errorJson = JSON.parse(errorText);
133
- if (errorJson.error?.message) {
134
- errorMessage = `OpenRouter: ${errorJson.error.message}`;
135
- }
136
- }
137
- catch {
138
- errorMessage += `: ${errorText.slice(0, 200)}`;
139
- }
140
- throw new Error(errorMessage);
141
- }
142
- const data = (await response.json());
143
- const content = data.choices?.[0]?.message?.content || "";
144
- const usage = data.usage || {};
145
- return {
146
- content,
147
- inputTokens: usage.prompt_tokens || estimateTokens(systemPrompt + userPrompt),
148
- outputTokens: usage.completion_tokens || estimateTokens(content),
149
- model: data.model || model,
150
- provider: "openrouter",
151
- };
152
- }
153
- /**
154
- * Calculate cost for OpenRouter request
155
- */
156
- export function calculateOpenRouterCost(model, inputTokens, outputTokens) {
157
- const modelConfig = OPENROUTER_MODELS[model];
158
- if (!modelConfig) {
159
- return ((inputTokens + outputTokens) / 1_000_000) * 1.0;
160
- }
161
- const inputCost = (inputTokens / 1_000_000) * modelConfig.inputCostPer1M;
162
- const outputCost = (outputTokens / 1_000_000) * modelConfig.outputCostPer1M;
163
- return inputCost + outputCost;
164
- }
165
94
  /**
166
95
  * Get list of recommended OpenRouter models for the UI
167
96
  */
@@ -1,16 +1,16 @@
1
1
  /**
2
- * TenantDO - Per-Tenant Durable Object
2
+ * TenantDO Types
3
3
  *
4
- * Provides:
5
- * - Config caching (eliminates D1 reads on every request)
6
- * - Cross-device draft sync
7
- * - Analytics event buffering
4
+ * Type definitions for the TenantDO Durable Object.
5
+ * The actual class implementation lives in packages/durable-objects/src/TenantDO.ts
6
+ * and is deployed as a separate Cloudflare Worker (grove-durable-objects).
8
7
  *
9
- * ID Pattern: tenant:{subdomain}
8
+ * The engine references this DO via service binding (script_name in wrangler.toml),
9
+ * so only types are needed here.
10
10
  *
11
11
  * Part of the Loom pattern - Grove's coordination layer.
12
12
  */
13
- import { type PaidTierKey } from "../config/tiers.js";
13
+ import type { PaidTierKey } from "../config/tiers.js";
14
14
  export interface TenantConfig {
15
15
  id: string;
16
16
  subdomain: string;
@@ -42,102 +42,3 @@ export interface AnalyticsEvent {
42
42
  data?: Record<string, unknown>;
43
43
  timestamp: number;
44
44
  }
45
- export declare class TenantDO implements DurableObject {
46
- private state;
47
- private env;
48
- private config;
49
- private configLoadedAt;
50
- private analyticsBuffer;
51
- private initialized;
52
- private refreshPromise;
53
- private subdomain;
54
- constructor(state: DurableObjectState, env: Env);
55
- /**
56
- * Initialize SQLite tables in DO storage
57
- */
58
- private initializeStorage;
59
- /**
60
- * Main request handler - routes to appropriate method
61
- */
62
- fetch(request: Request): Promise<Response>;
63
- /**
64
- * Get tenant config (cached in memory, refreshed from D1 if stale)
65
- *
66
- * Uses a promise lock pattern to prevent race conditions where multiple
67
- * concurrent requests all trigger D1 queries simultaneously.
68
- */
69
- private handleGetConfig;
70
- /**
71
- * Refresh config from DO storage or D1
72
- *
73
- * This method caches the tenant ID in the config to avoid repeated D1 lookups
74
- * in hooks.server.ts. The ID is stored alongside other config data.
75
- */
76
- private refreshConfig;
77
- /**
78
- * Update tenant config
79
- *
80
- * Uses the same promise lock pattern as handleGetConfig to prevent
81
- * race conditions where concurrent updates could clobber each other.
82
- */
83
- private handleUpdateConfig;
84
- /**
85
- * Get tier limits from centralized tiers.ts config
86
- *
87
- * This ensures TenantDO limits match the single source of truth,
88
- * preventing drift between DO cached limits and actual tier configuration.
89
- */
90
- private getTierLimits;
91
- /**
92
- * List all drafts for this tenant
93
- */
94
- private handleListDrafts;
95
- /**
96
- * Get a specific draft
97
- */
98
- private handleGetDraft;
99
- /**
100
- * Save or update a draft
101
- */
102
- private handleSaveDraft;
103
- /**
104
- * Delete a draft
105
- */
106
- private handleDeleteDraft;
107
- /**
108
- * Record an analytics event (buffered)
109
- */
110
- private handleRecordEvent;
111
- /**
112
- * Alarm handler - flush analytics buffer
113
- */
114
- alarm(): Promise<void>;
115
- /**
116
- * Flush analytics buffer to D1
117
- */
118
- private flushAnalytics;
119
- /**
120
- * Get the subdomain for this tenant DO
121
- *
122
- * Priority:
123
- * 1. Subdomain passed via X-Tenant-Subdomain header (set on every request)
124
- * 2. Cached subdomain from previous request
125
- * 3. Subdomain from cached config
126
- *
127
- * Returns null if subdomain is not yet known (shouldn't happen in practice
128
- * since hooks.server.ts always sends the header).
129
- */
130
- private getSubdomain;
131
- /**
132
- * Get the tenant ID (UUID) for this tenant
133
- *
134
- * Returns the cached ID from config, or null if not yet loaded.
135
- * The ID is fetched from D1 on first config load and cached.
136
- */
137
- private getTenantId;
138
- }
139
- interface Env {
140
- DB: D1Database;
141
- CACHE_KV: KVNamespace;
142
- }
143
- export {};