forktex-intelligence 0.2.3__py3-none-any.whl

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.
@@ -0,0 +1,623 @@
1
+ # Copyright (C) 2026 FORKTEX S.R.L.
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-ForkTex-Commercial
4
+ #
5
+ # This file is part of forktex-intelligence.
6
+ #
7
+ # For commercial licensing -- including use in proprietary products, SaaS
8
+ # deployments, or any context where AGPL obligations cannot be met -- you
9
+ # MUST obtain a commercial license from FORKTEX S.R.L. (info@forktex.com).
10
+ #
11
+ # This program is free software: you can redistribute it and/or modify
12
+ # it under the terms of the GNU Affero General Public License as published by
13
+ # the Free Software Foundation, either version 3 of the License, or
14
+ # (at your option) any later version.
15
+ #
16
+ # This program is distributed in the hope that it will be useful,
17
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
+ # GNU Affero General Public License for more details.
20
+ #
21
+ # You should have received a copy of the GNU Affero General Public License
22
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
23
+
24
+ """Auto-generated Pydantic models from the ForkTex Intelligence API OpenAPI spec.
25
+
26
+ DO NOT EDIT — regenerate with: make codegen
27
+
28
+ Source: GET /api/openapi.json
29
+ API version: 0.3.0
30
+ Spec hash: 16c8ac59984d62f3
31
+ """
32
+ from __future__ import annotations
33
+
34
+
35
+ SPEC_VERSION = "0.3.0"
36
+ SPEC_HASH = "16c8ac59984d62f3"
37
+
38
+
39
+ from typing import Annotated, Any
40
+ from uuid import UUID
41
+
42
+ from pydantic import AwareDatetime, BaseModel, Field
43
+
44
+
45
+ class APIKeyCreateRequest(BaseModel):
46
+ label: Annotated[str | None, Field(max_length=128, title="Label")] = ""
47
+
48
+
49
+ class APIKeyCreateResponse(BaseModel):
50
+ created_at: Annotated[AwareDatetime, Field(title="Created At")]
51
+ id: Annotated[UUID, Field(title="Id")]
52
+ label: Annotated[str, Field(title="Label")]
53
+ raw_key: Annotated[str, Field(title="Raw Key")]
54
+
55
+
56
+ class APIKeyResponse(BaseModel):
57
+ created_at: Annotated[AwareDatetime, Field(title="Created At")]
58
+ id: Annotated[UUID, Field(title="Id")]
59
+ is_active: Annotated[bool, Field(title="Is Active")]
60
+ label: Annotated[str, Field(title="Label")]
61
+
62
+
63
+ class BodyExtractApiOrgOrgIdExtractPost(BaseModel):
64
+ chunk_overlap: Annotated[int | None, Field(title="Chunk Overlap")] = 32
65
+ chunk_size: Annotated[int | None, Field(title="Chunk Size")] = 256
66
+ file: Annotated[str, Field(title="File")]
67
+
68
+
69
+ class BodyUploadDocApiOrgOrgIdCollectionsCollectionIdDocumentsPost(BaseModel):
70
+ file: Annotated[str, Field(title="File")]
71
+
72
+
73
+ class ChunkResult(BaseModel):
74
+ index: Annotated[int, Field(title="Index")]
75
+ text: Annotated[str, Field(title="Text")]
76
+ token_count: Annotated[int, Field(title="Token Count")]
77
+
78
+
79
+ class CollectionCreateRequest(BaseModel):
80
+ distance_metric: Annotated[str | None, Field(title="Distance Metric")] = "cosine"
81
+ embedding_model: Annotated[str | None, Field(title="Embedding Model")] = (
82
+ "all-MiniLM-L6-v2"
83
+ )
84
+ name: Annotated[str, Field(max_length=256, min_length=1, title="Name")]
85
+
86
+
87
+ class CollectionResponse(BaseModel):
88
+ created_at: Annotated[AwareDatetime, Field(title="Created At")]
89
+ dimensions: Annotated[int, Field(title="Dimensions")]
90
+ distance_metric: Annotated[str, Field(title="Distance Metric")]
91
+ document_count: Annotated[int, Field(title="Document Count")]
92
+ embedding_model: Annotated[str, Field(title="Embedding Model")]
93
+ id: Annotated[UUID, Field(title="Id")]
94
+ name: Annotated[str, Field(title="Name")]
95
+ updated_at: Annotated[AwareDatetime, Field(title="Updated At")]
96
+
97
+
98
+ class CrossCollectionSearchRequest(BaseModel):
99
+ collection_ids: Annotated[list[UUID] | None, Field(title="Collection Ids")] = None
100
+ query: Annotated[str, Field(min_length=1, title="Query")]
101
+ rerank: Annotated[bool | None, Field(title="Rerank")] = False
102
+ rerank_top_k: Annotated[int | None, Field(ge=1, le=100, title="Rerank Top K")] = 5
103
+ top_k: Annotated[int | None, Field(ge=1, le=100, title="Top K")] = 10
104
+
105
+
106
+ class DocumentResponse(BaseModel):
107
+ chunk_count: Annotated[int, Field(title="Chunk Count")]
108
+ collection_id: Annotated[UUID, Field(title="Collection Id")]
109
+ content_type: Annotated[str, Field(title="Content Type")]
110
+ created_at: Annotated[AwareDatetime, Field(title="Created At")]
111
+ error_message: Annotated[str | None, Field(title="Error Message")]
112
+ file_size_bytes: Annotated[int, Field(title="File Size Bytes")]
113
+ filename: Annotated[str, Field(title="Filename")]
114
+ id: Annotated[UUID, Field(title="Id")]
115
+ meta: Annotated[dict[str, Any] | None, Field(title="Meta")] = None
116
+ status: Annotated[str, Field(title="Status")]
117
+ updated_at: Annotated[AwareDatetime, Field(title="Updated At")]
118
+
119
+
120
+ class DocumentUploadResponse(BaseModel):
121
+ id: Annotated[UUID, Field(title="Id")]
122
+ message: Annotated[str | None, Field(title="Message")] = (
123
+ "Document accepted for processing"
124
+ )
125
+ status: Annotated[str, Field(title="Status")]
126
+
127
+
128
+ class EmbedRequest(BaseModel):
129
+ """
130
+ POST /api/v1/embed
131
+ """
132
+
133
+ input: Annotated[list[str], Field(max_length=256, min_length=1, title="Input")]
134
+ model: Annotated[str | None, Field(title="Model")] = "all-MiniLM-L6-v2"
135
+ normalize: Annotated[bool | None, Field(title="Normalize")] = True
136
+
137
+
138
+ class EmbeddingData(BaseModel):
139
+ """
140
+ Single embedding result.
141
+ """
142
+
143
+ embedding: Annotated[list[float], Field(title="Embedding")]
144
+ index: Annotated[int, Field(title="Index")]
145
+
146
+
147
+ class ExtractResponse(BaseModel):
148
+ chunks: Annotated[list[ChunkResult], Field(title="Chunks")]
149
+ content_type: Annotated[str, Field(title="Content Type")]
150
+ filename: Annotated[str, Field(title="Filename")]
151
+ metadata: Annotated[dict[str, Any] | None, Field(title="Metadata")] = None
152
+ text: Annotated[str, Field(title="Text")]
153
+
154
+
155
+ class HealthResponse(BaseModel):
156
+ status: Annotated[str, Field(title="Status")]
157
+ timestamp: Annotated[AwareDatetime, Field(title="Timestamp")]
158
+ version: Annotated[str, Field(title="Version")]
159
+
160
+
161
+ class LoginRequest(BaseModel):
162
+ email: Annotated[
163
+ str, Field(pattern="^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$", title="Email")
164
+ ]
165
+ password: Annotated[str, Field(title="Password")]
166
+
167
+
168
+ class MCPInfo(BaseModel):
169
+ description: Annotated[str, Field(title="Description")]
170
+ """
171
+ Human-readable summary
172
+ """
173
+ endpoint: Annotated[str, Field(title="Endpoint")]
174
+ """
175
+ HTTP path of the MCP JSON-RPC transport
176
+ """
177
+ protocol: Annotated[str, Field(title="Protocol")]
178
+ """
179
+ Protocol identifier
180
+ """
181
+ spec_url: Annotated[str, Field(title="Spec Url")]
182
+ """
183
+ Link to the MCP specification
184
+ """
185
+ version: Annotated[str, Field(title="Version")]
186
+ """
187
+ MCP protocol version this server targets
188
+ """
189
+
190
+
191
+ class ModelInfo(BaseModel):
192
+ aliases: Annotated[list[str] | None, Field(title="Aliases")] = None
193
+ capabilities: Annotated[list[str] | None, Field(title="Capabilities")] = None
194
+ context_window: Annotated[int | None, Field(title="Context Window")] = 0
195
+ description: Annotated[str | None, Field(title="Description")] = ""
196
+ id: Annotated[str, Field(title="Id")]
197
+ modality: Annotated[str | None, Field(title="Modality")] = "chat"
198
+
199
+
200
+ class ModelsResponse(BaseModel):
201
+ models: Annotated[list[ModelInfo], Field(title="Models")]
202
+
203
+
204
+ class OrgCreateRequest(BaseModel):
205
+ name: Annotated[str, Field(max_length=128, min_length=1, title="Name")]
206
+ slug: Annotated[
207
+ str,
208
+ Field(
209
+ max_length=128, min_length=1, pattern="^[a-z0-9][a-z0-9\\-]*$", title="Slug"
210
+ ),
211
+ ]
212
+
213
+
214
+ class OrgResponse(BaseModel):
215
+ created_at: Annotated[AwareDatetime, Field(title="Created At")]
216
+ id: Annotated[UUID, Field(title="Id")]
217
+ name: Annotated[str, Field(title="Name")]
218
+ slug: Annotated[str, Field(title="Slug")]
219
+
220
+
221
+ class RegisterRequest(BaseModel):
222
+ email: Annotated[
223
+ str, Field(pattern="^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$", title="Email")
224
+ ]
225
+ password: Annotated[str, Field(max_length=128, min_length=8, title="Password")]
226
+
227
+
228
+ class RerankRequest(BaseModel):
229
+ """
230
+ POST /api/v1/rerank
231
+ """
232
+
233
+ documents: Annotated[
234
+ list[str], Field(max_length=256, min_length=1, title="Documents")
235
+ ]
236
+ model: Annotated[str | None, Field(title="Model")] = "all-MiniLM-L6-v2"
237
+ query: Annotated[str, Field(title="Query")]
238
+ top_k: Annotated[int | None, Field(title="Top K")] = None
239
+
240
+
241
+ class RerankResult(BaseModel):
242
+ """
243
+ Single rerank result.
244
+ """
245
+
246
+ index: Annotated[int, Field(title="Index")]
247
+ score: Annotated[float, Field(title="Score")]
248
+ text: Annotated[str, Field(title="Text")]
249
+
250
+
251
+ class SearchRequest(BaseModel):
252
+ query: Annotated[str, Field(min_length=1, title="Query")]
253
+ rerank: Annotated[bool | None, Field(title="Rerank")] = False
254
+ rerank_top_k: Annotated[int | None, Field(ge=1, le=100, title="Rerank Top K")] = 5
255
+ top_k: Annotated[int | None, Field(ge=1, le=100, title="Top K")] = 10
256
+
257
+
258
+ class SearchResult(BaseModel):
259
+ chunk_id: Annotated[UUID, Field(title="Chunk Id")]
260
+ collection_id: Annotated[UUID, Field(title="Collection Id")]
261
+ document_id: Annotated[UUID, Field(title="Document Id")]
262
+ metadata: Annotated[dict[str, Any] | None, Field(title="Metadata")] = None
263
+ score: Annotated[float, Field(title="Score")]
264
+ text: Annotated[str, Field(title="Text")]
265
+
266
+
267
+ class TokenResponse(BaseModel):
268
+ access_token: Annotated[str, Field(title="Access Token")]
269
+ token_type: Annotated[str | None, Field(title="Token Type")] = "bearer"
270
+
271
+
272
+ class ToolCallInfo(BaseModel):
273
+ arguments: Annotated[dict[str, Any], Field(title="Arguments")]
274
+ id: Annotated[str, Field(title="Id")]
275
+ name: Annotated[str, Field(title="Name")]
276
+
277
+
278
+ class UsageAggregation(BaseModel):
279
+ avg_latency_ms: Annotated[float | None, Field(title="Avg Latency Ms")] = 0.0
280
+ by_api_key: Annotated[
281
+ dict[str, dict[str, Any]] | None, Field(title="By Api Key")
282
+ ] = None
283
+ by_endpoint: Annotated[dict[str, int] | None, Field(title="By Endpoint")] = None
284
+ by_model: Annotated[dict[str, int] | None, Field(title="By Model")] = None
285
+ by_user: Annotated[dict[str, dict[str, Any]] | None, Field(title="By User")] = None
286
+ currency: Annotated[str | None, Field(title="Currency")] = "usd"
287
+ period_end: Annotated[AwareDatetime | None, Field(title="Period End")] = None
288
+ period_start: Annotated[AwareDatetime | None, Field(title="Period Start")] = None
289
+ total_calls: Annotated[int | None, Field(title="Total Calls")] = 0
290
+ total_cost: Annotated[float | None, Field(title="Total Cost")] = 0.0
291
+ total_input_tokens: Annotated[int | None, Field(title="Total Input Tokens")] = 0
292
+ total_output_tokens: Annotated[int | None, Field(title="Total Output Tokens")] = 0
293
+ total_tokens: Annotated[int | None, Field(title="Total Tokens")] = 0
294
+
295
+
296
+ class UsageInfo(BaseModel):
297
+ input_tokens: Annotated[int | None, Field(title="Input Tokens")] = 0
298
+ model: Annotated[str | None, Field(title="Model")] = ""
299
+ output_tokens: Annotated[int | None, Field(title="Output Tokens")] = 0
300
+ timestamp: Annotated[float | None, Field(title="Timestamp")] = None
301
+ total_tokens: Annotated[int | None, Field(title="Total Tokens")] = 0
302
+
303
+
304
+ class UsageLogEntry(BaseModel):
305
+ api_key_id: Annotated[UUID | None, Field(title="Api Key Id")] = None
306
+ cost: Annotated[float | None, Field(title="Cost")] = None
307
+ created_at: Annotated[AwareDatetime, Field(title="Created At")]
308
+ currency: Annotated[str | None, Field(title="Currency")] = None
309
+ endpoint: Annotated[str, Field(title="Endpoint")]
310
+ error: Annotated[str | None, Field(title="Error")]
311
+ id: Annotated[UUID, Field(title="Id")]
312
+ input_tokens: Annotated[int, Field(title="Input Tokens")]
313
+ latency_ms: Annotated[int, Field(title="Latency Ms")]
314
+ method: Annotated[str, Field(title="Method")]
315
+ model: Annotated[str | None, Field(title="Model")]
316
+ output_tokens: Annotated[int, Field(title="Output Tokens")]
317
+ status_code: Annotated[int, Field(title="Status Code")]
318
+ user_id: Annotated[UUID | None, Field(title="User Id")] = None
319
+
320
+
321
+ class UsageLogResponse(BaseModel):
322
+ entries: Annotated[list[UsageLogEntry], Field(title="Entries")]
323
+ limit: Annotated[int, Field(title="Limit")]
324
+ offset: Annotated[int, Field(title="Offset")]
325
+ total: Annotated[int, Field(title="Total")]
326
+
327
+
328
+ class UserResponse(BaseModel):
329
+ created_at: Annotated[AwareDatetime, Field(title="Created At")]
330
+ email: Annotated[str, Field(title="Email")]
331
+ id: Annotated[UUID, Field(title="Id")]
332
+ is_active: Annotated[bool, Field(title="Is Active")]
333
+
334
+
335
+ class ValidationError(BaseModel):
336
+ ctx: Annotated[dict[str, Any] | None, Field(title="Context")] = None
337
+ input: Annotated[Any | None, Field(title="Input")] = None
338
+ loc: Annotated[list[str | int], Field(title="Location")]
339
+ msg: Annotated[str, Field(title="Message")]
340
+ type: Annotated[str, Field(title="Error Type")]
341
+
342
+
343
+ class ChatMessage(BaseModel):
344
+ """
345
+ Single message in the API request.
346
+ """
347
+
348
+ content: Annotated[str, Field(title="Content")]
349
+ role: Annotated[str, Field(title="Role")]
350
+ tool_call_id: Annotated[str | None, Field(title="Tool Call Id")] = None
351
+ tool_calls: Annotated[list[ToolCallInfo] | None, Field(title="Tool Calls")] = None
352
+
353
+
354
+ class ChatRequest(BaseModel):
355
+ """
356
+ POST /api/v1/chat
357
+ """
358
+
359
+ max_tokens: Annotated[int | None, Field(title="Max Tokens")] = None
360
+ messages: Annotated[list[ChatMessage], Field(title="Messages")]
361
+ model: Annotated[str | None, Field(title="Model")] = None
362
+ stream: Annotated[bool | None, Field(title="Stream")] = True
363
+ system: Annotated[str | None, Field(title="System")] = None
364
+ temperature: Annotated[float | None, Field(title="Temperature")] = None
365
+ tools: Annotated[list[dict[str, Any]] | None, Field(title="Tools")] = None
366
+
367
+
368
+ class ChatResponse(BaseModel):
369
+ """
370
+ Non-streaming chat response.
371
+ """
372
+
373
+ content: Annotated[str, Field(title="Content")]
374
+ model: Annotated[str, Field(title="Model")]
375
+ tool_calls: Annotated[list[ToolCallInfo] | None, Field(title="Tool Calls")] = None
376
+ usage: UsageInfo
377
+
378
+
379
+ class CollectionListResponse(BaseModel):
380
+ collections: Annotated[list[CollectionResponse], Field(title="Collections")]
381
+ total: Annotated[int, Field(title="Total")]
382
+
383
+
384
+ class DocumentListResponse(BaseModel):
385
+ documents: Annotated[list[DocumentResponse], Field(title="Documents")]
386
+ total: Annotated[int, Field(title="Total")]
387
+
388
+
389
+ class EmbedResponse(BaseModel):
390
+ """
391
+ Embedding response — compatible with OpenAI format.
392
+ """
393
+
394
+ data: Annotated[list[EmbeddingData], Field(title="Data")]
395
+ dimensions: Annotated[int, Field(title="Dimensions")]
396
+ model: Annotated[str, Field(title="Model")]
397
+ usage: Annotated[dict[str, int] | None, Field(title="Usage")] = None
398
+
399
+
400
+ class HTTPValidationError(BaseModel):
401
+ detail: Annotated[list[ValidationError] | None, Field(title="Detail")] = None
402
+
403
+
404
+ class RerankResponse(BaseModel):
405
+ data: Annotated[list[RerankResult], Field(title="Data")]
406
+ model: Annotated[str, Field(title="Model")]
407
+
408
+
409
+ class SearchResponse(BaseModel):
410
+ query: Annotated[str, Field(title="Query")]
411
+ results: Annotated[list[SearchResult], Field(title="Results")]
412
+ total: Annotated[int, Field(title="Total")]
413
+
414
+
415
+ class StructuredChatRequest(BaseModel):
416
+ """
417
+ POST /api/v1/chat/structured
418
+ """
419
+
420
+ messages: Annotated[list[ChatMessage], Field(title="Messages")]
421
+ model: Annotated[str | None, Field(title="Model")] = None
422
+ response_schema: Annotated[
423
+ dict[str, Any] | None, Field(title="Response Schema")
424
+ ] = None
425
+ system: Annotated[str | None, Field(title="System")] = None
426
+
427
+
428
+ class StructuredChatResponse(BaseModel):
429
+ """
430
+ Structured (JSON-mode) response.
431
+ """
432
+
433
+ content: Annotated[str, Field(title="Content")]
434
+ model: Annotated[str, Field(title="Model")]
435
+ usage: UsageInfo
436
+
437
+
438
+ class UserMeResponse(BaseModel):
439
+ orgs: Annotated[list[OrgResponse], Field(title="Orgs")]
440
+ user: UserResponse
441
+
442
+ # ── Generated async operations ──────────────────────────────────────
443
+ #
444
+ # One method per OpenAPI operation, typed with the generated Pydantic
445
+ # models above. Subclasses must implement ``_request``.
446
+
447
+ class _GeneratedOperations:
448
+ """Async method-per-operation client surface generated from openapi.json."""
449
+
450
+ async def _request(
451
+ self, method: str, path: str, *, params: dict[str, Any] | None = None,
452
+ json: Any = None,
453
+ ) -> Any:
454
+ raise NotImplementedError
455
+
456
+ async def login(self, body: LoginRequest) -> TokenResponse:
457
+ """Login"""
458
+ _resp = await self._request("POST", '/api/auth/login', json=body.model_dump(mode="json", exclude_unset=True) if body is not None else None)
459
+ return TokenResponse.model_validate(_resp)
460
+
461
+ async def register(self, body: RegisterRequest) -> None:
462
+ """Register"""
463
+ _resp = await self._request("POST", '/api/auth/register', json=body.model_dump(mode="json", exclude_unset=True) if body is not None else None)
464
+ return None
465
+
466
+ async def health(self) -> HealthResponse:
467
+ """Health"""
468
+ _resp = await self._request("GET", '/api/health')
469
+ return HealthResponse.model_validate(_resp)
470
+
471
+ async def health_ready(self) -> None:
472
+ """Health Ready"""
473
+ _resp = await self._request("GET", '/api/health/ready')
474
+ return None
475
+
476
+ async def mcp_info(self) -> MCPInfo:
477
+ """Mcp Info"""
478
+ _resp = await self._request("GET", '/api/mcp/info')
479
+ return MCPInfo.model_validate(_resp)
480
+
481
+ async def me(self) -> UserMeResponse:
482
+ """Me"""
483
+ _resp = await self._request("GET", '/api/me')
484
+ return UserMeResponse.model_validate(_resp)
485
+
486
+ async def list_models(self) -> ModelsResponse:
487
+ """List Models"""
488
+ _resp = await self._request("GET", '/api/models')
489
+ return ModelsResponse.model_validate(_resp)
490
+
491
+ async def list_keys(self, org_id: UUID) -> list[APIKeyResponse]:
492
+ """List Keys"""
493
+ _resp = await self._request("GET", f'/api/org/{org_id}/api-keys')
494
+ return [APIKeyResponse.model_validate(_x) for _x in _resp]
495
+
496
+ async def create_key(self, org_id: UUID, body: APIKeyCreateRequest) -> APIKeyCreateResponse:
497
+ """Create Key"""
498
+ _resp = await self._request("POST", f'/api/org/{org_id}/api-keys', json=body.model_dump(mode="json", exclude_unset=True) if body is not None else None)
499
+ return APIKeyCreateResponse.model_validate(_resp)
500
+
501
+ async def delete_key(self, org_id: UUID, key_id: UUID) -> None:
502
+ """Delete Key"""
503
+ _resp = await self._request("DELETE", f'/api/org/{org_id}/api-keys/{key_id}')
504
+ return None
505
+
506
+ async def chat(self, org_id: UUID, body: ChatRequest) -> ChatResponse:
507
+ """Chat"""
508
+ _resp = await self._request("POST", f'/api/org/{org_id}/chat', json=body.model_dump(mode="json", exclude_unset=True) if body is not None else None)
509
+ return ChatResponse.model_validate(_resp)
510
+
511
+ async def chat_structured(self, org_id: UUID, body: StructuredChatRequest) -> StructuredChatResponse:
512
+ """Chat Structured"""
513
+ _resp = await self._request("POST", f'/api/org/{org_id}/chat/structured', json=body.model_dump(mode="json", exclude_unset=True) if body is not None else None)
514
+ return StructuredChatResponse.model_validate(_resp)
515
+
516
+ async def list_colls(self, org_id: UUID) -> CollectionListResponse:
517
+ """List Colls"""
518
+ _resp = await self._request("GET", f'/api/org/{org_id}/collections')
519
+ return CollectionListResponse.model_validate(_resp)
520
+
521
+ async def create_coll(self, org_id: UUID, body: CollectionCreateRequest) -> CollectionResponse:
522
+ """Create Coll"""
523
+ _resp = await self._request("POST", f'/api/org/{org_id}/collections', json=body.model_dump(mode="json", exclude_unset=True) if body is not None else None)
524
+ return CollectionResponse.model_validate(_resp)
525
+
526
+ async def delete_coll(self, org_id: UUID, collection_id: UUID) -> None:
527
+ """Delete Coll"""
528
+ _resp = await self._request("DELETE", f'/api/org/{org_id}/collections/{collection_id}')
529
+ return None
530
+
531
+ async def get_coll(self, org_id: UUID, collection_id: UUID) -> CollectionResponse:
532
+ """Get Coll"""
533
+ _resp = await self._request("GET", f'/api/org/{org_id}/collections/{collection_id}')
534
+ return CollectionResponse.model_validate(_resp)
535
+
536
+ async def list_docs(self, org_id: UUID, collection_id: UUID) -> DocumentListResponse:
537
+ """List Docs"""
538
+ _resp = await self._request("GET", f'/api/org/{org_id}/collections/{collection_id}/documents')
539
+ return DocumentListResponse.model_validate(_resp)
540
+
541
+ async def upload_doc(self, org_id: UUID, collection_id: UUID) -> DocumentUploadResponse:
542
+ """Upload Doc"""
543
+ _resp = await self._request("POST", f'/api/org/{org_id}/collections/{collection_id}/documents')
544
+ return DocumentUploadResponse.model_validate(_resp)
545
+
546
+ async def delete_doc(self, org_id: UUID, collection_id: UUID, document_id: UUID) -> None:
547
+ """Delete Doc"""
548
+ _resp = await self._request("DELETE", f'/api/org/{org_id}/collections/{collection_id}/documents/{document_id}')
549
+ return None
550
+
551
+ async def get_doc(self, org_id: UUID, collection_id: UUID, document_id: UUID) -> DocumentResponse:
552
+ """Get Doc"""
553
+ _resp = await self._request("GET", f'/api/org/{org_id}/collections/{collection_id}/documents/{document_id}')
554
+ return DocumentResponse.model_validate(_resp)
555
+
556
+ async def search_single(self, org_id: UUID, collection_id: UUID, body: SearchRequest) -> SearchResponse:
557
+ """Search Single"""
558
+ _resp = await self._request("POST", f'/api/org/{org_id}/collections/{collection_id}/search', json=body.model_dump(mode="json", exclude_unset=True) if body is not None else None)
559
+ return SearchResponse.model_validate(_resp)
560
+
561
+ async def embed(self, org_id: UUID, body: EmbedRequest) -> EmbedResponse:
562
+ """Embed"""
563
+ _resp = await self._request("POST", f'/api/org/{org_id}/embed', json=body.model_dump(mode="json", exclude_unset=True) if body is not None else None)
564
+ return EmbedResponse.model_validate(_resp)
565
+
566
+ async def rerank(self, org_id: UUID, body: RerankRequest) -> RerankResponse:
567
+ """Rerank"""
568
+ _resp = await self._request("POST", f'/api/org/{org_id}/embed/rerank', json=body.model_dump(mode="json", exclude_unset=True) if body is not None else None)
569
+ return RerankResponse.model_validate(_resp)
570
+
571
+ async def extract(self, org_id: UUID) -> ExtractResponse:
572
+ """Extract"""
573
+ _resp = await self._request("POST", f'/api/org/{org_id}/extract')
574
+ return ExtractResponse.model_validate(_resp)
575
+
576
+ async def search_cross(self, org_id: UUID, body: CrossCollectionSearchRequest) -> SearchResponse:
577
+ """Search Cross"""
578
+ _resp = await self._request("POST", f'/api/org/{org_id}/search', json=body.model_dump(mode="json", exclude_unset=True) if body is not None else None)
579
+ return SearchResponse.model_validate(_resp)
580
+
581
+ async def usage_stats(self, org_id: UUID, since: AwareDatetime | None = None, until: AwareDatetime | None = None) -> UsageAggregation:
582
+ """Usage Stats"""
583
+ _q: dict[str, Any] = {}
584
+ if since is not None:
585
+ _q['since'] = since
586
+ if until is not None:
587
+ _q['until'] = until
588
+ _resp = await self._request("GET", f'/api/org/{org_id}/usage', params=_q or None)
589
+ return UsageAggregation.model_validate(_resp)
590
+
591
+ async def usage_log(self, org_id: UUID, offset: int | None = None, limit: int | None = None, since: AwareDatetime | None = None, until: AwareDatetime | None = None, api_key_id: UUID | None = None, user_id: UUID | None = None) -> UsageLogResponse:
592
+ """Usage Log"""
593
+ _q: dict[str, Any] = {}
594
+ if offset is not None:
595
+ _q['offset'] = offset
596
+ if limit is not None:
597
+ _q['limit'] = limit
598
+ if since is not None:
599
+ _q['since'] = since
600
+ if until is not None:
601
+ _q['until'] = until
602
+ if api_key_id is not None:
603
+ _q['api_key_id'] = api_key_id
604
+ if user_id is not None:
605
+ _q['user_id'] = user_id
606
+ _resp = await self._request("GET", f'/api/org/{org_id}/usage/log', params=_q or None)
607
+ return UsageLogResponse.model_validate(_resp)
608
+
609
+ async def list_orgs(self) -> list[OrgResponse]:
610
+ """List Orgs"""
611
+ _resp = await self._request("GET", '/api/orgs')
612
+ return [OrgResponse.model_validate(_x) for _x in _resp]
613
+
614
+ async def create_org_endpoint(self, body: OrgCreateRequest) -> OrgResponse:
615
+ """Create Org Endpoint"""
616
+ _resp = await self._request("POST", '/api/orgs', json=body.model_dump(mode="json", exclude_unset=True) if body is not None else None)
617
+ return OrgResponse.model_validate(_resp)
618
+
619
+ async def whoami(self) -> None:
620
+ """Whoami"""
621
+ _resp = await self._request("GET", '/api/whoami')
622
+ return None
623
+
@@ -0,0 +1,43 @@
1
+ # Copyright (C) 2026 FORKTEX S.R.L.
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-ForkTex-Commercial
4
+ #
5
+ # This file is part of forktex-intelligence.
6
+ #
7
+ # For commercial licensing -- including use in proprietary products, SaaS
8
+ # deployments, or any context where AGPL obligations cannot be met -- you
9
+ # MUST obtain a commercial license from FORKTEX S.R.L. (info@forktex.com).
10
+ #
11
+ # This program is free software: you can redistribute it and/or modify
12
+ # it under the terms of the GNU Affero General Public License as published by
13
+ # the Free Software Foundation, either version 3 of the License, or
14
+ # (at your option) any later version.
15
+ #
16
+ # This program is distributed in the hope that it will be useful,
17
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
+ # GNU Affero General Public License for more details.
20
+ #
21
+ # You should have received a copy of the GNU Affero General Public License
22
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
23
+
24
+ """Intelligence configuration model.
25
+
26
+ Pure data model — no filesystem I/O. The host application (e.g. forktex-py)
27
+ is responsible for discovering, loading, and persisting settings.
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ from pydantic import BaseModel
33
+
34
+
35
+ class IntelligenceSettings(BaseModel):
36
+ """Configuration for the ForkTex Intelligence API client."""
37
+
38
+ endpoint: str = "https://intelligence.forktex.com/api"
39
+ api_key: str = ""
40
+
41
+ @property
42
+ def is_configured(self) -> bool:
43
+ return bool(self.endpoint and self.api_key)