@realtimex/folio 0.1.2
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/.env.example +20 -0
- package/README.md +63 -0
- package/api/server.ts +130 -0
- package/api/src/config/index.ts +96 -0
- package/api/src/middleware/auth.ts +128 -0
- package/api/src/middleware/errorHandler.ts +88 -0
- package/api/src/middleware/index.ts +4 -0
- package/api/src/middleware/rateLimit.ts +71 -0
- package/api/src/middleware/validation.ts +58 -0
- package/api/src/routes/accounts.ts +142 -0
- package/api/src/routes/baseline-config.ts +124 -0
- package/api/src/routes/chat.ts +154 -0
- package/api/src/routes/health.ts +61 -0
- package/api/src/routes/index.ts +35 -0
- package/api/src/routes/ingestions.ts +275 -0
- package/api/src/routes/migrate.ts +112 -0
- package/api/src/routes/policies.ts +121 -0
- package/api/src/routes/processing.ts +90 -0
- package/api/src/routes/rules.ts +11 -0
- package/api/src/routes/sdk.ts +100 -0
- package/api/src/routes/settings.ts +80 -0
- package/api/src/routes/setup.ts +389 -0
- package/api/src/routes/stats.ts +81 -0
- package/api/src/routes/tts.ts +190 -0
- package/api/src/services/BaselineConfigService.ts +208 -0
- package/api/src/services/ChatService.ts +204 -0
- package/api/src/services/GoogleDriveService.ts +331 -0
- package/api/src/services/GoogleSheetsService.ts +1107 -0
- package/api/src/services/IngestionService.ts +1187 -0
- package/api/src/services/ModelCapabilityService.ts +248 -0
- package/api/src/services/PolicyEngine.ts +1625 -0
- package/api/src/services/PolicyLearningService.ts +527 -0
- package/api/src/services/PolicyLoader.ts +249 -0
- package/api/src/services/RAGService.ts +391 -0
- package/api/src/services/SDKService.ts +249 -0
- package/api/src/services/supabase.ts +113 -0
- package/api/src/utils/Actuator.ts +284 -0
- package/api/src/utils/actions/ActionHandler.ts +34 -0
- package/api/src/utils/actions/AppendToGSheetAction.ts +260 -0
- package/api/src/utils/actions/AutoRenameAction.ts +58 -0
- package/api/src/utils/actions/CopyAction.ts +120 -0
- package/api/src/utils/actions/CopyToGDriveAction.ts +64 -0
- package/api/src/utils/actions/LogCsvAction.ts +48 -0
- package/api/src/utils/actions/NotifyAction.ts +39 -0
- package/api/src/utils/actions/RenameAction.ts +57 -0
- package/api/src/utils/actions/WebhookAction.ts +58 -0
- package/api/src/utils/actions/utils.ts +293 -0
- package/api/src/utils/llmResponse.ts +61 -0
- package/api/src/utils/logger.ts +67 -0
- package/bin/folio-deploy.js +12 -0
- package/bin/folio-setup.js +45 -0
- package/bin/folio.js +65 -0
- package/dist/api/server.js +106 -0
- package/dist/api/src/config/index.js +81 -0
- package/dist/api/src/middleware/auth.js +93 -0
- package/dist/api/src/middleware/errorHandler.js +73 -0
- package/dist/api/src/middleware/index.js +4 -0
- package/dist/api/src/middleware/rateLimit.js +43 -0
- package/dist/api/src/middleware/validation.js +54 -0
- package/dist/api/src/routes/accounts.js +110 -0
- package/dist/api/src/routes/baseline-config.js +91 -0
- package/dist/api/src/routes/chat.js +114 -0
- package/dist/api/src/routes/health.js +52 -0
- package/dist/api/src/routes/index.js +31 -0
- package/dist/api/src/routes/ingestions.js +207 -0
- package/dist/api/src/routes/migrate.js +91 -0
- package/dist/api/src/routes/policies.js +86 -0
- package/dist/api/src/routes/processing.js +75 -0
- package/dist/api/src/routes/rules.js +8 -0
- package/dist/api/src/routes/sdk.js +80 -0
- package/dist/api/src/routes/settings.js +68 -0
- package/dist/api/src/routes/setup.js +315 -0
- package/dist/api/src/routes/stats.js +62 -0
- package/dist/api/src/routes/tts.js +178 -0
- package/dist/api/src/services/BaselineConfigService.js +168 -0
- package/dist/api/src/services/ChatService.js +166 -0
- package/dist/api/src/services/GoogleDriveService.js +280 -0
- package/dist/api/src/services/GoogleSheetsService.js +795 -0
- package/dist/api/src/services/IngestionService.js +990 -0
- package/dist/api/src/services/ModelCapabilityService.js +179 -0
- package/dist/api/src/services/PolicyEngine.js +1353 -0
- package/dist/api/src/services/PolicyLearningService.js +397 -0
- package/dist/api/src/services/PolicyLoader.js +159 -0
- package/dist/api/src/services/RAGService.js +295 -0
- package/dist/api/src/services/SDKService.js +212 -0
- package/dist/api/src/services/supabase.js +72 -0
- package/dist/api/src/utils/Actuator.js +225 -0
- package/dist/api/src/utils/actions/ActionHandler.js +1 -0
- package/dist/api/src/utils/actions/AppendToGSheetAction.js +191 -0
- package/dist/api/src/utils/actions/AutoRenameAction.js +49 -0
- package/dist/api/src/utils/actions/CopyAction.js +112 -0
- package/dist/api/src/utils/actions/CopyToGDriveAction.js +55 -0
- package/dist/api/src/utils/actions/LogCsvAction.js +42 -0
- package/dist/api/src/utils/actions/NotifyAction.js +32 -0
- package/dist/api/src/utils/actions/RenameAction.js +51 -0
- package/dist/api/src/utils/actions/WebhookAction.js +51 -0
- package/dist/api/src/utils/actions/utils.js +237 -0
- package/dist/api/src/utils/llmResponse.js +63 -0
- package/dist/api/src/utils/logger.js +51 -0
- package/dist/assets/index-DzN8-j-e.css +1 -0
- package/dist/assets/index-Uy-ai3Dh.js +113 -0
- package/dist/favicon.svg +31 -0
- package/dist/folio-logo.svg +46 -0
- package/dist/index.html +14 -0
- package/docs-dev/FPE-spec.md +196 -0
- package/docs-dev/folio-prd.md +47 -0
- package/docs-dev/foundation-checklist.md +30 -0
- package/docs-dev/hybrid-routing-architecture.md +205 -0
- package/docs-dev/ingestion-engine.md +69 -0
- package/docs-dev/port-from-email-automator.md +32 -0
- package/docs-dev/tech-spec.md +98 -0
- package/index.html +13 -0
- package/package.json +101 -0
- package/public/favicon.svg +31 -0
- package/public/folio-logo.svg +46 -0
- package/scripts/dev-task.mjs +51 -0
- package/scripts/get-latest-migration-timestamp.mjs +34 -0
- package/scripts/migrate.sh +91 -0
- package/supabase/.temp/cli-latest +1 -0
- package/supabase/.temp/gotrue-version +1 -0
- package/supabase/.temp/pooler-url +1 -0
- package/supabase/.temp/postgres-version +1 -0
- package/supabase/.temp/project-ref +1 -0
- package/supabase/.temp/rest-version +1 -0
- package/supabase/.temp/storage-migration +1 -0
- package/supabase/.temp/storage-version +1 -0
- package/supabase/config.toml +64 -0
- package/supabase/functions/_shared/auth.ts +35 -0
- package/supabase/functions/_shared/cors.ts +12 -0
- package/supabase/functions/_shared/supabaseAdmin.ts +17 -0
- package/supabase/functions/api-v1-settings/index.ts +66 -0
- package/supabase/functions/setup/index.ts +91 -0
- package/supabase/migrations/20260223000000_initial_foundation.sql +136 -0
- package/supabase/migrations/20260223000001_add_migration_rpc.sql +10 -0
- package/supabase/migrations/20260224000002_add_init_state_view.sql +20 -0
- package/supabase/migrations/20260224000003_port_user_creation_parity.sql +139 -0
- package/supabase/migrations/20260224000004_add_avatars_storage.sql +26 -0
- package/supabase/migrations/20260224000005_add_tts_and_embed_settings.sql +24 -0
- package/supabase/migrations/20260224000006_add_policies_table.sql +48 -0
- package/supabase/migrations/20260224000007_fix_migration_rpc.sql +9 -0
- package/supabase/migrations/20260224000008_add_ingestions_table.sql +42 -0
- package/supabase/migrations/20260225000000_setup_compatible_mode.sql +119 -0
- package/supabase/migrations/20260225000001_restore_ingestions.sql +49 -0
- package/supabase/migrations/20260225000002_add_ingestion_trace.sql +2 -0
- package/supabase/migrations/20260225000003_add_baseline_configs.sql +35 -0
- package/supabase/migrations/20260226000000_add_processing_events.sql +26 -0
- package/supabase/migrations/20260226000001_add_ingestion_file_hash.sql +10 -0
- package/supabase/migrations/20260226000002_add_dynamic_rag.sql +150 -0
- package/supabase/migrations/20260226000003_add_ingestion_summary.sql +4 -0
- package/supabase/migrations/20260226000004_add_ingestion_tags.sql +7 -0
- package/supabase/migrations/20260226000005_add_chat_tables.sql +60 -0
- package/supabase/migrations/20260227000000_harden_chat_messages_rls.sql +25 -0
- package/supabase/migrations/20260228000000_add_vision_model_capabilities.sql +8 -0
- package/supabase/migrations/20260228000001_add_policy_match_feedback.sql +51 -0
- package/supabase/migrations/29991231235959_test_migration.sql +0 -0
- package/supabase/templates/confirmation.html +76 -0
- package/supabase/templates/email-change.html +76 -0
- package/supabase/templates/invite.html +72 -0
- package/supabase/templates/magic-link.html +68 -0
- package/supabase/templates/recovery.html +82 -0
- package/tsconfig.api.json +16 -0
- package/tsconfig.json +25 -0
- package/vite.config.ts +146 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import type { SupabaseClient } from "@supabase/supabase-js";
|
|
2
|
+
import { createLogger } from "../utils/logger.js";
|
|
3
|
+
import { SDKService } from "./SDKService.js";
|
|
4
|
+
|
|
5
|
+
const logger = createLogger("ModelCapabilityService");
|
|
6
|
+
|
|
7
|
+
export type VisionCapabilityState = "supported" | "unsupported" | "unknown";
|
|
8
|
+
|
|
9
|
+
interface StoredVisionCapability {
|
|
10
|
+
state: "supported" | "unsupported";
|
|
11
|
+
learned_at: string;
|
|
12
|
+
expires_at?: string;
|
|
13
|
+
reason?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type VisionCapabilityMap = Record<string, StoredVisionCapability>;
|
|
17
|
+
|
|
18
|
+
interface SettingsLike {
|
|
19
|
+
llm_provider?: string | null;
|
|
20
|
+
llm_model?: string | null;
|
|
21
|
+
vision_model_capabilities?: unknown;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface VisionResolution {
|
|
25
|
+
provider: string;
|
|
26
|
+
model: string;
|
|
27
|
+
state: VisionCapabilityState;
|
|
28
|
+
shouldAttempt: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class ModelCapabilityService {
|
|
32
|
+
private static readonly SUPPORTED_TTL_DAYS = 180;
|
|
33
|
+
private static readonly UNSUPPORTED_TTL_DAYS = 30;
|
|
34
|
+
|
|
35
|
+
static resolveVisionSupport(settingsRow: SettingsLike | null | undefined): VisionResolution {
|
|
36
|
+
const provider = (settingsRow?.llm_provider || SDKService.DEFAULT_LLM_PROVIDER).trim();
|
|
37
|
+
const model = (settingsRow?.llm_model || SDKService.DEFAULT_LLM_MODEL).trim();
|
|
38
|
+
const state = this.getVisionState(settingsRow?.vision_model_capabilities, provider, model);
|
|
39
|
+
return {
|
|
40
|
+
provider,
|
|
41
|
+
model,
|
|
42
|
+
state,
|
|
43
|
+
shouldAttempt: state !== "unsupported",
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static getVisionState(rawMap: unknown, provider: string, model: string): VisionCapabilityState {
|
|
48
|
+
const map = this.normalizeCapabilityMap(rawMap);
|
|
49
|
+
const entry = map[this.capabilityKey(provider, model)];
|
|
50
|
+
if (!entry) return "unknown";
|
|
51
|
+
|
|
52
|
+
if (entry.expires_at) {
|
|
53
|
+
const expiryTs = Date.parse(entry.expires_at);
|
|
54
|
+
if (Number.isFinite(expiryTs) && expiryTs <= Date.now()) {
|
|
55
|
+
return "unknown";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return entry.state;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static async learnVisionSuccess(opts: {
|
|
63
|
+
supabase: SupabaseClient;
|
|
64
|
+
userId: string;
|
|
65
|
+
provider: string;
|
|
66
|
+
model: string;
|
|
67
|
+
}): Promise<void> {
|
|
68
|
+
await this.writeCapability({
|
|
69
|
+
...opts,
|
|
70
|
+
state: "supported",
|
|
71
|
+
reason: "vision_request_succeeded",
|
|
72
|
+
ttlDays: this.SUPPORTED_TTL_DAYS,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static async learnVisionFailure(opts: {
|
|
77
|
+
supabase: SupabaseClient;
|
|
78
|
+
userId: string;
|
|
79
|
+
provider: string;
|
|
80
|
+
model: string;
|
|
81
|
+
error: unknown;
|
|
82
|
+
}): Promise<VisionCapabilityState> {
|
|
83
|
+
const classification = this.classifyVisionFailure(opts.error);
|
|
84
|
+
if (!classification.isCapabilityError) {
|
|
85
|
+
logger.info(`Vision failure for ${opts.provider}/${opts.model} treated as transient; leaving capability unknown`, {
|
|
86
|
+
reason: classification.reason,
|
|
87
|
+
});
|
|
88
|
+
return "unknown";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
await this.writeCapability({
|
|
92
|
+
supabase: opts.supabase,
|
|
93
|
+
userId: opts.userId,
|
|
94
|
+
provider: opts.provider,
|
|
95
|
+
model: opts.model,
|
|
96
|
+
state: "unsupported",
|
|
97
|
+
reason: classification.reason,
|
|
98
|
+
ttlDays: this.UNSUPPORTED_TTL_DAYS,
|
|
99
|
+
});
|
|
100
|
+
return "unsupported";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private static async writeCapability(opts: {
|
|
104
|
+
supabase: SupabaseClient;
|
|
105
|
+
userId: string;
|
|
106
|
+
provider: string;
|
|
107
|
+
model: string;
|
|
108
|
+
state: "supported" | "unsupported";
|
|
109
|
+
reason: string;
|
|
110
|
+
ttlDays: number;
|
|
111
|
+
}): Promise<void> {
|
|
112
|
+
const { supabase, userId, provider, model, state, reason, ttlDays } = opts;
|
|
113
|
+
const { data, error: readErr } = await supabase
|
|
114
|
+
.from("user_settings")
|
|
115
|
+
.select("vision_model_capabilities")
|
|
116
|
+
.eq("user_id", userId)
|
|
117
|
+
.maybeSingle();
|
|
118
|
+
|
|
119
|
+
if (readErr) {
|
|
120
|
+
logger.warn("Failed to read user_settings for model capability write", { userId, readErr });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const map = this.normalizeCapabilityMap(data?.vision_model_capabilities);
|
|
125
|
+
const now = new Date();
|
|
126
|
+
const expiresAt = new Date(now.getTime() + ttlDays * 24 * 60 * 60 * 1000).toISOString();
|
|
127
|
+
map[this.capabilityKey(provider, model)] = {
|
|
128
|
+
state,
|
|
129
|
+
learned_at: now.toISOString(),
|
|
130
|
+
expires_at: expiresAt,
|
|
131
|
+
reason,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const { error: writeErr } = await supabase
|
|
135
|
+
.from("user_settings")
|
|
136
|
+
.upsert(
|
|
137
|
+
{
|
|
138
|
+
user_id: userId,
|
|
139
|
+
vision_model_capabilities: map,
|
|
140
|
+
},
|
|
141
|
+
{ onConflict: "user_id" }
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
if (writeErr) {
|
|
145
|
+
logger.warn("Failed to persist model capability state", { userId, provider, model, state, writeErr });
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
logger.info(`Updated model capability for ${provider}/${model}: ${state}`, { reason, ttlDays });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private static normalizeCapabilityMap(rawMap: unknown): VisionCapabilityMap {
|
|
153
|
+
if (!rawMap || typeof rawMap !== "object" || Array.isArray(rawMap)) {
|
|
154
|
+
return {};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const parsed = rawMap as Record<string, unknown>;
|
|
158
|
+
const normalized: VisionCapabilityMap = {};
|
|
159
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
160
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const state = String((value as Record<string, unknown>).state || "");
|
|
164
|
+
if (state !== "supported" && state !== "unsupported") {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const learnedAt = (value as Record<string, unknown>).learned_at;
|
|
169
|
+
const expiresAt = (value as Record<string, unknown>).expires_at;
|
|
170
|
+
const reason = (value as Record<string, unknown>).reason;
|
|
171
|
+
|
|
172
|
+
normalized[key] = {
|
|
173
|
+
state,
|
|
174
|
+
learned_at: typeof learnedAt === "string" ? learnedAt : new Date(0).toISOString(),
|
|
175
|
+
expires_at: typeof expiresAt === "string" ? expiresAt : undefined,
|
|
176
|
+
reason: typeof reason === "string" ? reason : undefined,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return normalized;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private static capabilityKey(provider: string, model: string): string {
|
|
184
|
+
return `${provider.toLowerCase().trim()}:${model.toLowerCase().trim()}`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private static classifyVisionFailure(error: unknown): { isCapabilityError: boolean; reason: string } {
|
|
188
|
+
const message = this.errorToMessage(error).toLowerCase();
|
|
189
|
+
if (!message) return { isCapabilityError: false, reason: "empty_error" };
|
|
190
|
+
|
|
191
|
+
const capabilityHints = [
|
|
192
|
+
"image_url",
|
|
193
|
+
"vision",
|
|
194
|
+
"multimodal",
|
|
195
|
+
"multi-modal",
|
|
196
|
+
"unsupported content type",
|
|
197
|
+
"unsupported message content",
|
|
198
|
+
"does not support images",
|
|
199
|
+
"model does not support image",
|
|
200
|
+
"invalid content type",
|
|
201
|
+
"invalid image",
|
|
202
|
+
"unrecognized content type",
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
if (capabilityHints.some((hint) => message.includes(hint))) {
|
|
206
|
+
return { isCapabilityError: true, reason: "capability_mismatch" };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const transientHints = [
|
|
210
|
+
"timeout",
|
|
211
|
+
"timed out",
|
|
212
|
+
"rate limit",
|
|
213
|
+
"too many requests",
|
|
214
|
+
"429",
|
|
215
|
+
"503",
|
|
216
|
+
"502",
|
|
217
|
+
"504",
|
|
218
|
+
"service unavailable",
|
|
219
|
+
"temporar",
|
|
220
|
+
"network",
|
|
221
|
+
"connection",
|
|
222
|
+
"unauthorized",
|
|
223
|
+
"forbidden",
|
|
224
|
+
"invalid api key",
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
if (transientHints.some((hint) => message.includes(hint))) {
|
|
228
|
+
return { isCapabilityError: false, reason: "transient_or_auth" };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return { isCapabilityError: false, reason: "unknown_error_class" };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private static errorToMessage(error: unknown): string {
|
|
235
|
+
if (error instanceof Error) return error.message;
|
|
236
|
+
if (typeof error === "string") return error;
|
|
237
|
+
if (error && typeof error === "object") {
|
|
238
|
+
const candidate = error as Record<string, unknown>;
|
|
239
|
+
if (typeof candidate.message === "string") return candidate.message;
|
|
240
|
+
try {
|
|
241
|
+
return JSON.stringify(error);
|
|
242
|
+
} catch {
|
|
243
|
+
return String(error);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return String(error ?? "");
|
|
247
|
+
}
|
|
248
|
+
}
|