@appaflytech/zeyrek-ai-mcp 0.0.5 → 0.0.7
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 +2 -2
- package/.env.production.example +9 -1
- package/README.md +14 -38
- package/dist/config.js +36 -3
- package/dist/knowledge/backendConstraints.js +982 -0
- package/dist/knowledge/componentCatalog.js +549 -92
- package/dist/knowledge/componentEvents.js +120 -25
- package/dist/knowledge/coverage.js +43 -0
- package/dist/knowledge/flowGuards.js +30 -0
- package/dist/knowledge/functionGuards.js +134 -0
- package/dist/knowledge/guideSections.js +49 -0
- package/dist/knowledge/hydrate.js +97 -0
- package/dist/knowledge/languageCodes.js +36 -0
- package/dist/knowledge/languages.js +105 -0
- package/dist/knowledge/layoutSummary.js +10 -0
- package/dist/knowledge/localization.js +51 -0
- package/dist/knowledge/normalize.js +500 -16
- package/dist/knowledge/outlines.js +167 -12
- package/dist/knowledge/policy.js +14 -5
- package/dist/knowledge/propTypes.js +80 -0
- package/dist/knowledge/rules.js +961 -44
- package/dist/knowledge/schemaGuard.js +199 -0
- package/dist/knowledge/styleResolve.js +63 -4
- package/dist/observability/trace.js +27 -3
- package/dist/platform/csv.js +123 -0
- package/dist/platform/errors.js +107 -6
- package/dist/platform/http.js +34 -4
- package/dist/platform/patch.js +27 -0
- package/dist/platform/refs.js +104 -0
- package/dist/platform/responses.js +25 -6
- package/dist/platform/screenshot.js +544 -16
- package/dist/platform/secrets.js +40 -0
- package/dist/platform/tokenVault.js +89 -0
- package/dist/platform/writeDiff.js +281 -0
- package/dist/resources/examples/flow-call-function.json +66 -0
- package/dist/resources/examples/function-total.json +70 -0
- package/dist/resources/examples/layout-app-shell.json +1 -1
- package/dist/resources/examples/microfunction-node-sum.json +56 -0
- package/dist/resources/examples/microservice-table-list.json +2 -4
- package/dist/resources/examples/page-dynamic-form.json +3 -3
- package/dist/resources/examples/page-flex-sections.json +99 -0
- package/dist/resources/examples/page-sign-in.json +4 -3
- package/dist/resources/guides/component.md +81 -8
- package/dist/resources/guides/entity.md +712 -20
- package/dist/resources/guides/expressions.md +47 -2
- package/dist/resources/guides/external-component.md +9 -0
- package/dist/resources/guides/functions.md +262 -0
- package/dist/resources/guides/getting-started.md +82 -7
- package/dist/resources/guides/layout.md +53 -10
- package/dist/resources/guides/merge.md +231 -0
- package/dist/resources/guides/microfunction.md +166 -0
- package/dist/resources/guides/microservice.md +287 -15
- package/dist/resources/guides/middleware.md +89 -1
- package/dist/resources/guides/modify-existing.md +23 -0
- package/dist/resources/guides/page.md +402 -49
- package/dist/resources/guides/translation.md +150 -8
- package/dist/resources/guides/view.md +16 -1
- package/dist/resources/guides/workflow.md +199 -28
- package/dist/resources/knowledge.js +5 -0
- package/dist/resources/rules.js +27 -0
- package/dist/resources/schema-fingerprint.json +8 -0
- package/dist/resources/schemas/AnalyzeImportQuery.json +51 -0
- package/dist/resources/schemas/ApplyMergePlanCommand.json +19 -0
- package/dist/resources/schemas/CreateBulkRowCommand.json +36 -0
- package/dist/resources/schemas/CreateDatasourceCommand.json +15 -6
- package/dist/resources/schemas/CreateDiscoveredEntitiesCommand.json +19 -8
- package/dist/resources/schemas/CreateEntityCommand.json +52 -12
- package/dist/resources/schemas/CreateEnvironmentCommand.json +31 -0
- package/dist/resources/schemas/CreateGlobalFunctionCommand.json +95 -0
- package/dist/resources/schemas/CreateImportPlanCommand.json +278 -0
- package/dist/resources/schemas/CreateMergePlanCommand.json +34 -0
- package/dist/resources/schemas/CreateMicrofunctionCommand.json +117 -0
- package/dist/resources/schemas/CreateMicrofunctionRuntimeCommand.json +51 -0
- package/dist/resources/schemas/CreateMicroserviceCommand.json +145 -3
- package/dist/resources/schemas/CreateQueryCommand.json +56 -3
- package/dist/resources/schemas/CreateRowCommand.json +38 -0
- package/dist/resources/schemas/CreateViewCommand.json +13 -10
- package/dist/resources/schemas/CreateWorkflowCommand.json +106 -14
- package/dist/resources/schemas/DataJobDto.json +267 -0
- package/dist/resources/schemas/ExportRowsCommand.json +172 -0
- package/dist/resources/schemas/Filter.json +3 -1
- package/dist/resources/schemas/GetRowsQuery.json +6 -1
- package/dist/resources/schemas/GlobalFunctionDto.json +89 -0
- package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +249 -0
- package/dist/resources/schemas/ImportAnalysisDto.json +401 -0
- package/dist/resources/schemas/ImportPlanDto.json +260 -0
- package/dist/resources/schemas/ImportSampleDto.json +90 -0
- package/dist/resources/schemas/InvokeMicrofunctionCommand.json +25 -0
- package/dist/resources/schemas/PreviewMicroserviceCommand.json +135 -1
- package/dist/resources/schemas/PreviewQueryCommand.json +5 -2
- package/dist/resources/schemas/PreviewWorkflowCommand.json +45 -9
- package/dist/resources/schemas/ResolveMergeConflictsCommand.json +58 -0
- package/dist/resources/schemas/RevertMergePlanCommand.json +19 -0
- package/dist/resources/schemas/RunImportCommand.json +62 -0
- package/dist/resources/schemas/StepDto.json +34 -3
- package/dist/resources/schemas/UpdateBulkRowCommand.json +36 -0
- package/dist/resources/schemas/UpdateDatasourceCommand.json +15 -6
- package/dist/resources/schemas/UpdateEntityCommand.json +52 -11
- package/dist/resources/schemas/UpdateGlobalFunctionCommand.json +66 -0
- package/dist/resources/schemas/UpdateMicrofunctionCommand.json +121 -0
- package/dist/resources/schemas/UpdateMicrofunctionRuntimeCommand.json +54 -0
- package/dist/resources/schemas/UpdateMicroserviceCommand.json +147 -4
- package/dist/resources/schemas/UpdateQueryCommand.json +13 -3
- package/dist/resources/schemas/UpdateRowCommand.json +40 -0
- package/dist/resources/schemas/UpdateViewCommand.json +15 -10
- package/dist/resources/schemas/UpdateWorkflowCommand.json +106 -14
- package/dist/server.js +17 -1
- package/dist/tools/access.js +3 -3
- package/dist/tools/activities.js +40 -0
- package/dist/tools/applications.js +1 -1
- package/dist/tools/automations.js +76 -34
- package/dist/tools/common.js +55 -0
- package/dist/tools/components.js +16 -7
- package/dist/tools/datasources.js +83 -21
- package/dist/tools/dependencies.js +1 -1
- package/dist/tools/deployments.js +34 -2
- package/dist/tools/dtos.js +25 -12
- package/dist/tools/entities.js +113 -28
- package/dist/tools/environments.js +52 -0
- package/dist/tools/externalComponents.js +9 -3
- package/dist/tools/files.js +57 -42
- package/dist/tools/flows.js +205 -13
- package/dist/tools/folders.js +7 -2
- package/dist/tools/functions.js +331 -0
- package/dist/tools/globalFunctions.js +63 -0
- package/dist/tools/importSchemas.js +79 -0
- package/dist/tools/imports.js +241 -0
- package/dist/tools/layouts.js +103 -29
- package/dist/tools/merges.js +113 -0
- package/dist/tools/microfunctions.js +155 -0
- package/dist/tools/microservices.js +270 -40
- package/dist/tools/organizations.js +5 -6
- package/dist/tools/pages.js +421 -104
- package/dist/tools/platformInfo.js +155 -11
- package/dist/tools/projects.js +183 -33
- package/dist/tools/queries.js +32 -10
- package/dist/tools/revisions.js +94 -0
- package/dist/tools/rows.js +484 -50
- package/dist/tools/screenWrite.js +68 -0
- package/dist/tools/screens.js +28 -0
- package/dist/tools/templates.js +32 -5
- package/dist/tools/translations.js +24 -8
- package/dist/tools/views.js +20 -10
- package/dist/tools/workflows.js +56 -21
- package/package.json +7 -2
package/dist/tools/entities.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { guid, projectParam, environmentParam, definitionParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
|
|
1
|
+
import { guid, entityRef, resolveEntityId, withResolved, projectParam, environmentParam, definitionParam, pagingParams, readOnly, writes, updates, destructive, microOps } from './common.js';
|
|
2
|
+
import { RefResolver, isGuid } from '../platform/refs.js';
|
|
2
3
|
import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
|
|
3
4
|
import { splitJunctionFields, withNormalization } from '../knowledge/normalize.js';
|
|
4
|
-
import {
|
|
5
|
+
import { unmappedMembers, introducedUnmappedMembers, withUnmappedMembers } from '../knowledge/schemaGuard.js';
|
|
6
|
+
import { entityOutline, resolvePart, resolvePatchOperations, patchedSections } from '../knowledge/outlines.js';
|
|
7
|
+
import { patchResult, baseVersionOf } from '../platform/patch.js';
|
|
5
8
|
import { isJunctionField, policyRules } from '../knowledge/rules.js';
|
|
6
9
|
import { handle } from '../platform/responses.js';
|
|
7
10
|
import fastJsonPatch from 'fast-json-patch';
|
|
@@ -20,6 +23,52 @@ function preserveApplicationBinding(definition) {
|
|
|
20
23
|
if (applicationIds.length > 0)
|
|
21
24
|
definition.applicationIds = applicationIds;
|
|
22
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* `UpdateEntityCommandHandler` gövdedeki `Indexes` null ise onu BOŞ LİSTEYE çeviriyor
|
|
28
|
+
* (`request.Indexes?…ToList() ?? []`) ve `Entity.FindProcessedIndexes` boş listede entity'nin
|
|
29
|
+
* bütün index'lerini silinecek kümeye koyuyor: kayıt düşer, `DROP INDEX` koşar, hata dönmez.
|
|
30
|
+
* Domain katmanı null'ı özellikle koruyor, komut katmanı bu korumayı geçersiz kılıyor.
|
|
31
|
+
*
|
|
32
|
+
* Gateway alan verilmemişse mevcut index'leri gövdeye taşır. Bilinçli silme yolu kapanmaz:
|
|
33
|
+
* `indexes: []` AÇIKÇA yazılmışsa buraya hiç girilmez.
|
|
34
|
+
*/
|
|
35
|
+
async function carryIndexes(platform, resolver, project, environment, datasourceId, id, body) {
|
|
36
|
+
const entity = datasourceId && isGuid(id) ? { entityId: id, datasourceId } : await resolver.entity(id);
|
|
37
|
+
const current = await platform.tenant('GET', project, `/entities/${entity.datasourceId}/${entity.entityId}`, { environment });
|
|
38
|
+
const definition = (current?.data ?? current);
|
|
39
|
+
const indexes = Array.isArray(definition?.indexes) ? definition.indexes : [];
|
|
40
|
+
if (indexes.length === 0)
|
|
41
|
+
return [];
|
|
42
|
+
body.indexes = indexes;
|
|
43
|
+
return [`Gövdede 'indexes' yoktu: platform bu durumda entity'nin BÜTÜN index'lerini siler, bu yüzden mevcut ${indexes.length} index gövdeye taşındı. Silmek istiyorsanız 'indexes: []' yazarak açıkça isteyin.`];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* `Entity.FindProcessedIndexes` farkı YALNIZ `id` üzerinden alıyor: aynı `id` ile gelen index ne
|
|
47
|
+
* eklenenler ne silinenler kümesinde olduğu için `EntityService.UpdateAsync` ona hiç dokunmuyor.
|
|
48
|
+
* Ad, unique bayrağı ya da alan listesi değişse bile ne kayıt ne DDL güncellenir; istek 200 döner
|
|
49
|
+
* ve yama uygulanmış görünür. Politika kuralı olamaz — kural yalnız yamalanmış gövdeyi görür,
|
|
50
|
+
* bu karşılaştırma ÖNCEKİ hâli de ister.
|
|
51
|
+
*/
|
|
52
|
+
function inPlaceIndexEdits(before, after) {
|
|
53
|
+
const asIndexes = (value) => {
|
|
54
|
+
const source = (value && typeof value === 'object' ? value : {});
|
|
55
|
+
return Array.isArray(source.indexes)
|
|
56
|
+
? source.indexes.filter((index) => Boolean(index) && typeof index === 'object')
|
|
57
|
+
: [];
|
|
58
|
+
};
|
|
59
|
+
const shape = (index) => JSON.stringify([index.name ?? null, index.unique ?? false, Array.isArray(index.fieldIds) ? index.fieldIds : []]);
|
|
60
|
+
const original = new Map(asIndexes(before)
|
|
61
|
+
.filter(index => typeof index.id === 'string')
|
|
62
|
+
.map(index => [index.id, shape(index)]));
|
|
63
|
+
return asIndexes(after)
|
|
64
|
+
.filter(index => typeof index.id === 'string' && original.has(index.id) && original.get(index.id) !== shape(index))
|
|
65
|
+
.map(index => ({
|
|
66
|
+
ruleId: 'ENT-014',
|
|
67
|
+
severity: 'warn',
|
|
68
|
+
message: `'${index.id}' id'li index'in tanımı değişti ama platform var olan bir index'i YERİNDE GÜNCELLEMEZ: farkı yalnız 'id' üzerinden alır, aynı id'yi taşıyan kaydı ne siler ne yeniden kurar. İstek 200 dönse de veritabanında hiçbir şey değişmez. Index'i kaldırıp yeni bir id ile yeniden ekleyin.`,
|
|
69
|
+
guide: 'ngapps://guides/entity'
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
23
72
|
export function registerEntityTools(server, platform) {
|
|
24
73
|
const config = platform.config;
|
|
25
74
|
server.registerTool('entity_list', {
|
|
@@ -38,39 +87,42 @@ export function registerEntityTools(server, platform) {
|
|
|
38
87
|
}, handle(config, ({ project, environment, datasourceId, ...query }) => platform.tenant('GET', project, `/entities/${datasourceId}`, { environment, query })));
|
|
39
88
|
server.registerTool('entity_get', {
|
|
40
89
|
title: 'Entity getir',
|
|
41
|
-
description: "Entity'yi alan tanımlarıyla getirir. Çok alanlı entity'de önce view:'outline' (alan listesi), sonra view:'part' ile tek alan çekin.",
|
|
90
|
+
description: "Entity'yi alan tanımlarıyla getirir. Çok alanlı entity'de önce view:'outline' (alan listesi), sonra view:'part' ile tek alan çekin. Çok dilli entity'de kök alanlar companion bağını taşır: parent'ta `localizationEntityId`, companion'da `localizationParentId` — companion'ı ad kalıbından TAHMİN ETMEYİN, bu bağdan bulun.",
|
|
42
91
|
inputSchema: {
|
|
43
92
|
project: projectParam,
|
|
44
93
|
environment: environmentParam,
|
|
45
|
-
datasourceId: guid,
|
|
46
|
-
id:
|
|
94
|
+
datasourceId: guid.optional().describe('Verilmezse gateway datasource ağacından bulur'),
|
|
95
|
+
id: entityRef,
|
|
47
96
|
view: z.enum(['full', 'outline', 'part']).optional().describe('varsayılan full'),
|
|
48
97
|
part: z.string().optional().describe("view:'part' için yol, ör. fields[slug=email] veya indexes[0]")
|
|
49
98
|
},
|
|
50
99
|
annotations: readOnly
|
|
51
100
|
}, handle(config, async ({ project, environment, datasourceId, id, view, part }) => {
|
|
52
|
-
const
|
|
101
|
+
const resolver = new RefResolver(platform, project, environment);
|
|
102
|
+
const entity = datasourceId && isGuid(id) ? { entityId: id, datasourceId } : await resolver.entity(id);
|
|
103
|
+
const payload = await platform.tenant('GET', project, `/entities/${entity.datasourceId}/${entity.entityId}`, { environment });
|
|
53
104
|
const definition = (payload?.data ?? payload);
|
|
54
105
|
if (view === 'outline')
|
|
55
|
-
return entityOutline(definition);
|
|
106
|
+
return withResolved(entityOutline(definition), resolver.resolved);
|
|
56
107
|
if (view === 'part') {
|
|
57
108
|
if (!part)
|
|
58
109
|
throw new Error("view:'part' için 'part' parametresi zorunlu.");
|
|
59
|
-
return { part, value: resolvePart(definition, part) };
|
|
110
|
+
return withResolved({ part, value: resolvePart(definition, part) }, resolver.resolved);
|
|
60
111
|
}
|
|
61
|
-
return payload;
|
|
112
|
+
return withResolved(payload, resolver.resolved);
|
|
62
113
|
}));
|
|
63
114
|
server.registerTool('entity_create', {
|
|
64
115
|
title: 'Entity oluştur',
|
|
65
|
-
description: "Datasource altında yeni entity oluşturur (definition = CreateEntityCommand: name, fields[], applicationIds, ...). İlişkiler ve alan tipi kararı için platform_guide('entity') okuyun. Gövde politika kontrolünden geçer. applicationIds create'te platformca bağlanır (CreateEntityCommandHandler entityApplications'ı Entity ctor'una geçirir); satır uçları bu bağa İHTİYAÇ DUYMAZ — bağ yalnız Studio'nun application'a göre entity listesini ve bağımlılık sorgularını etkiler.",
|
|
116
|
+
description: "Datasource altında yeni entity oluşturur (definition = CreateEntityCommand: name, fields[], applicationIds, ...). İlişkiler ve alan tipi kararı için platform_guide('entity') okuyun. Gövde politika kontrolünden geçer. applicationIds create'te platformca bağlanır (CreateEntityCommandHandler entityApplications'ı Entity ctor'una geçirir); satır uçları bu bağa İHTİYAÇ DUYMAZ — bağ yalnız Studio'nun application'a göre entity listesini ve bağımlılık sorgularını etkiler. `fields[].isLocalized: true` alanı ana tablodan çıkarıp companion `<slug>_localization` entity'sine taşır (yalnız düz METİN alanı; anahtar/unique/ilişki olamaz) — platform_guide('entity') §'Çoklu dilli alan'.",
|
|
66
117
|
inputSchema: { project: projectParam, environment: environmentParam, datasourceId: guid, definition: definitionParam, policyOverride: policyOverrideParam },
|
|
67
118
|
annotations: writes
|
|
68
119
|
}, handle(config, async ({ project, environment, datasourceId, definition, policyOverride }) => {
|
|
69
120
|
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'entity', definition, { operation: 'create' }, policyOverride));
|
|
70
121
|
const { base, deferred } = splitJunctionFields(definition, isJunctionField);
|
|
122
|
+
const drift = unmappedMembers('CreateEntityCommand', base);
|
|
71
123
|
const payload = await platform.tenant('POST', project, `/entities/${datasourceId}`, { environment, body: base });
|
|
72
124
|
if (deferred.length === 0)
|
|
73
|
-
return attachFindings(payload, findings);
|
|
125
|
+
return attachFindings(withUnmappedMembers(payload, drift), findings);
|
|
74
126
|
const created = (payload?.data ?? payload);
|
|
75
127
|
const createdId = String(created?.id ?? '');
|
|
76
128
|
if (!createdId) {
|
|
@@ -82,7 +134,7 @@ export function registerEntityTools(server, platform) {
|
|
|
82
134
|
preserveApplicationBinding(full);
|
|
83
135
|
const updated = await platform.tenant('PUT', project, `/entities/${createdId}`, { environment, body: full });
|
|
84
136
|
const note = `m2m/çoklu alanlar (${deferred.map(field => field.slug ?? field.name).join(', ')}) ayrı bir güncelleme turunda eklendi: create yolu junction kaydını açar ama fiziksel tabloyu kurmaz (platform bug'ı P-01), update yolu kurar.`;
|
|
85
|
-
return attachFindings(withNormalization(updated, [note]), findings);
|
|
137
|
+
return attachFindings(withUnmappedMembers(withNormalization(updated, [note]), drift), findings);
|
|
86
138
|
}));
|
|
87
139
|
server.registerTool('entity_create_discovered', {
|
|
88
140
|
title: 'Keşfedilen entity\'leri içe al',
|
|
@@ -92,34 +144,67 @@ export function registerEntityTools(server, platform) {
|
|
|
92
144
|
}, handle(config, ({ project, environment, datasourceId, definition }) => platform.tenant('POST', project, `/entities/${datasourceId}/discovered`, { environment, body: definition })));
|
|
93
145
|
server.registerTool('entity_update', {
|
|
94
146
|
title: 'Entity güncelle',
|
|
95
|
-
description:
|
|
96
|
-
inputSchema: { project: projectParam, environment: environmentParam,
|
|
147
|
+
description: "Entity tanımını günceller (tam gövde). Hedefli değişiklik için entity_patch tercih edin. Gövde politika kontrolünden geçer. DİKKAT: gövdede 'indexes' YOKSA platform entity'nin bütün index'lerini siler — gateway alan verilmemişse mevcut index'leri taşır ve bunu bildirir; silmek için 'indexes: []' yazın. `fields[].isLocalized: true` alanı ana tablodan çıkarıp companion `<slug>_localization` entity'sine taşır (yalnız düz METİN alanı; anahtar/unique/ilişki olamaz) — platform_guide('entity') §'Çoklu dilli alan'.",
|
|
148
|
+
inputSchema: { project: projectParam, environment: environmentParam, datasourceId: guid.optional().describe('Verilmezse gateway datasource ağacından bulur'), id: entityRef, definition: definitionParam, policyOverride: policyOverrideParam },
|
|
97
149
|
annotations: updates
|
|
98
|
-
}, handle(config, async ({ project, environment, id, definition, policyOverride }) => {
|
|
150
|
+
}, handle(config, async ({ project, environment, datasourceId, id, definition, policyOverride }) => {
|
|
99
151
|
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'entity', definition, { operation: 'update' }, policyOverride));
|
|
100
|
-
const
|
|
101
|
-
|
|
152
|
+
const resolver = new RefResolver(platform, project, environment);
|
|
153
|
+
const body = { ...definition };
|
|
154
|
+
const notes = Object.hasOwn(body, 'indexes') ? [] : await carryIndexes(platform, resolver, project, environment, datasourceId, id, body);
|
|
155
|
+
const entityId = await resolveEntityId(resolver, id);
|
|
156
|
+
const drift = unmappedMembers('UpdateEntityCommand', definition);
|
|
157
|
+
const payload = await platform.tenant('PUT', project, `/entities/${entityId}`, { environment, body });
|
|
158
|
+
return attachFindings(withUnmappedMembers(withResolved(withNormalization(payload, notes), resolver.resolved), drift), findings);
|
|
102
159
|
}));
|
|
103
160
|
server.registerTool('entity_patch', {
|
|
104
161
|
title: 'Entity\'ye patch uygula',
|
|
105
|
-
description: "Tam gövdeyi bağlama almadan hedefli değişiklik: gateway mevcut tanımı
|
|
106
|
-
inputSchema: { project: projectParam, environment: environmentParam, datasourceId: guid, id:
|
|
162
|
+
description: "Tam gövdeyi bağlama almadan hedefli değişiklik: gateway mevcut tanımı okur, yolları çözer ve op'ları platformun PATCH /entities/{id} ucuna `{ baseVersion, ops }` olarak geçirir — tam gövde PUT'u ATILMAZ, yalnız fark yazılır. Yanıt outline özetidir. YOLLARDA SEÇİCİ KULLANIN, indeks değil: '/fields[slug=email]/nullable' — indeksle yazmak alan sırası değişince SESSİZCE yanlış alanı yazar. Gateway seçiciyi indekse çevirir ve hangi kayda indiğini yanıttaki patchTargets ile söyler. `baseVersion` okunan kaydın `version` alanından gelir; kayıt bu arada değiştiyse platform 409 döner ve HİÇBİR op uygulanmaz. 'applications' → 'applicationIds' çevirisini artık platform yapar. `fields[].isLocalized: true` alanı ana tablodan çıkarıp companion `<slug>_localization` entity'sine taşır (yalnız düz METİN alanı; anahtar/unique/ilişki olamaz) — platform_guide('entity') §'Çoklu dilli alan'.",
|
|
163
|
+
inputSchema: { project: projectParam, environment: environmentParam, datasourceId: guid.optional().describe('Verilmezse gateway datasource ağacından bulur'), id: entityRef, operations: patchOperationsParam.optional(), ops: patchOperationsParam.optional().describe("'operations' ile aynı alan (eşanlamlı) — sayfa micro-op tool'larıyla aynı adı kullanabilmeniz için"), policyOverride: policyOverrideParam },
|
|
107
164
|
annotations: updates
|
|
108
|
-
}, handle(config, async ({ project, environment, datasourceId, id, operations, policyOverride }) => {
|
|
109
|
-
const
|
|
165
|
+
}, handle(config, async ({ project, environment, datasourceId, id, operations: givenOperations, ops, policyOverride }) => {
|
|
166
|
+
const operations = microOps('entity_patch', ops, givenOperations);
|
|
167
|
+
const resolver = new RefResolver(platform, project, environment);
|
|
168
|
+
const entity = datasourceId && isGuid(id) ? { entityId: id, datasourceId } : await resolver.entity(id);
|
|
169
|
+
const current = await platform.tenant('GET', project, `/entities/${entity.datasourceId}/${entity.entityId}`, { environment });
|
|
110
170
|
const definition = structuredClone((current?.data ?? current));
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
171
|
+
const resolvedOps = resolvePatchOperations(definition, operations, 'entity_get');
|
|
172
|
+
// Politika, yazmadan önce sonucu görmeli; yerel kopya YALNIZ değerlendirme için, gövde olarak gönderilmez.
|
|
173
|
+
const preview = fastJsonPatch.applyPatch(definition, resolvedOps.operations, true, false).newDocument;
|
|
174
|
+
const findings = [
|
|
175
|
+
...enforcePolicy(evaluatePolicy(config, policyRules, 'entity', preview, { operation: 'update' }, policyOverride)),
|
|
176
|
+
...inPlaceIndexEdits(current?.data ?? current, preview)
|
|
177
|
+
];
|
|
178
|
+
// Yalnız YAMANIN eklediği anahtar bildirilir: gövdenin geri kalanını gateway GET'ten taşıdı
|
|
179
|
+
// ve orada komutta olmayan yedi yapısal alan var (ölçüldü — g-03 Adım 0). Onları bildirmek
|
|
180
|
+
// kanalı gürültüye boğar ve gerçek sinyali gömer.
|
|
181
|
+
const drift = introducedUnmappedMembers('UpdateEntityCommand', (current?.data ?? current), preview);
|
|
182
|
+
const saved = await platform.tenant('PATCH', project, `/entities/${entity.entityId}`, {
|
|
183
|
+
environment,
|
|
184
|
+
body: { baseVersion: baseVersionOf(definition, 'entity_get'), ops: resolvedOps.operations }
|
|
185
|
+
});
|
|
186
|
+
const written = patchResult(saved, preview);
|
|
187
|
+
const touched = patchedSections(written.data, resolvedOps.operations);
|
|
188
|
+
return attachFindings(withUnmappedMembers(withResolved({
|
|
189
|
+
applied: operations.length,
|
|
190
|
+
newVersion: written.newVersion,
|
|
191
|
+
patchTargets: resolvedOps.targets,
|
|
192
|
+
...(resolvedOps.notes.length > 0 ? { resolvedPaths: resolvedOps.notes } : {}),
|
|
193
|
+
// S-8: yazılanın kanıtı yanıtta dursun; dokunulmayan bölümler outline'da kalır.
|
|
194
|
+
...(Object.keys(touched).length > 0 ? { touched } : {}),
|
|
195
|
+
outline: entityOutline(written.data)
|
|
196
|
+
}, resolver.resolved), drift), findings);
|
|
116
197
|
}));
|
|
117
198
|
server.registerTool('entity_delete', {
|
|
118
199
|
title: 'Entity sil',
|
|
119
200
|
description: 'Entity tanımını siler; önce entity_dependencies ile bağımlılıklarını kontrol edin.',
|
|
120
|
-
inputSchema: { project: projectParam, environment: environmentParam, id:
|
|
201
|
+
inputSchema: { project: projectParam, environment: environmentParam, id: entityRef },
|
|
121
202
|
annotations: destructive
|
|
122
|
-
}, handle(config, ({ project, environment, id }) =>
|
|
203
|
+
}, handle(config, async ({ project, environment, id }) => {
|
|
204
|
+
const resolver = new RefResolver(platform, project, environment);
|
|
205
|
+
const entityId = await resolveEntityId(resolver, id);
|
|
206
|
+
return withResolved(await platform.tenant('DELETE', project, `/entities/${entityId}`, { environment }), resolver.resolved);
|
|
207
|
+
}));
|
|
123
208
|
server.registerTool('entity_clone', {
|
|
124
209
|
title: 'Entity klonla',
|
|
125
210
|
description: 'Entity\'nin kopyasını oluşturur.',
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { branchNamePattern, destructive, guid, isReservedEnvironmentName, readOnly, writes } from './common.js';
|
|
2
|
+
import { PlatformApiError } from '../platform/errors.js';
|
|
3
|
+
import { handle } from '../platform/responses.js';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
/** Aggregate kuralının aynası (`Environment` ctor); gerçek koruma backend'dedir. Kural `common.ts`'te. */
|
|
6
|
+
const branchName = z.string().regex(branchNamePattern)
|
|
7
|
+
.describe("Dal adı: küçük harfle başlar, yalnız küçük harf/rakam/alt çizgi, 3-31 karakter. Bu ad PG şema adı olur.");
|
|
8
|
+
function assertBranchName(name) {
|
|
9
|
+
if (isReservedEnvironmentName(name)) {
|
|
10
|
+
throw new PlatformApiError(`environment_create: '${name}' rezerve bir şema adı — dal adı olarak kullanılamaz. Başka bir ad seçin.`, 400);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function registerEnvironmentTools(server, platform) {
|
|
14
|
+
const config = platform.config;
|
|
15
|
+
server.registerTool('environment_list', {
|
|
16
|
+
title: 'Environment listesi',
|
|
17
|
+
description: "Projenin aktif environment'larını döner: önce sabitler (development, production), sonra dallar. Her satırda 'state' vardır (creating | ready | merging | deleting | merged | error); yalnız 'ready' bir dalda çalışılabilir. Dalda çalışmak için tenant tool'larına environment: '<dal adı>' geçin.",
|
|
18
|
+
inputSchema: { projectId: guid },
|
|
19
|
+
annotations: readOnly
|
|
20
|
+
}, handle(config, ({ projectId }) => platform.org('GET', `/environments/${projectId}`)));
|
|
21
|
+
server.registerTool('environment_create', {
|
|
22
|
+
title: 'Dal environment\'ı aç',
|
|
23
|
+
description: "Projede yeni bir dal environment'ı açar: kendi PG şeması kurulur ve development'ın tanımları oraya kopyalanır. Asenkron bir saga'dır — yanıt 202 döner, dal hemen kullanılabilir DEĞİLDİR. includeRecords true ise projenin KENDİ veritabanındaki bütün veri tablolarının satırları da kopyalanır (çok dilli companion tablolar ve junction'lar dahil; files/folders ve audit trail tabloları hariç, dış datasource'lara dokunulmaz) — entity'de kayıt taşıması ayarı açık olmasa bile. Satırlar merge'de geri taşınmaz.",
|
|
24
|
+
inputSchema: { projectId: guid, name: branchName, includeRecords: z.boolean().optional().describe('Projenin kendi veritabanındaki bütün tabloların satırları da kopyalansın mı (varsayılan false)') },
|
|
25
|
+
annotations: writes
|
|
26
|
+
}, handle(config, async ({ projectId, name, includeRecords }) => {
|
|
27
|
+
assertBranchName(name);
|
|
28
|
+
const payload = await platform.org('POST', `/environments/${projectId}`, { body: { name, includeRecords: includeRecords === true } });
|
|
29
|
+
return {
|
|
30
|
+
...(payload && typeof payload === 'object' ? payload : { data: payload }),
|
|
31
|
+
note: "dal asenkron açılıyor; environment_list ile state=ready bekleyin"
|
|
32
|
+
};
|
|
33
|
+
}));
|
|
34
|
+
server.registerTool('environment_delete', {
|
|
35
|
+
title: 'Dal environment\'ını sil',
|
|
36
|
+
description: "Bir dal environment'ını ve PG şemasını siler. Sabit environment'lar (development, production) silinemez — gateway bunu HTTP'den önce reddeder. Yalnız 'ready' ya da 'error' durumundaki dal silinebilir; silme asenkrondur.",
|
|
37
|
+
inputSchema: { projectId: guid, environmentId: guid },
|
|
38
|
+
annotations: destructive
|
|
39
|
+
}, handle(config, async ({ projectId, environmentId }) => {
|
|
40
|
+
const environments = await platform.org('GET', `/environments/${projectId}`);
|
|
41
|
+
const rows = environments?.data ?? [];
|
|
42
|
+
const target = rows.find(row => row.id === environmentId);
|
|
43
|
+
if (target && target.isBranch !== true) {
|
|
44
|
+
throw new PlatformApiError(`environment_delete: '${String(target.name)}' sabit bir environment — silinemez. Yalnız dallar silinir (environment_list çıktısında isBranch: true olanlar).`, 400);
|
|
45
|
+
}
|
|
46
|
+
const payload = await platform.org('DELETE', `/environments/${projectId}/${environmentId}`);
|
|
47
|
+
return {
|
|
48
|
+
...(payload && typeof payload === 'object' ? payload : { data: payload }),
|
|
49
|
+
note: "şema asenkron düşürülüyor; environment_list ile dalın listeden çıkmasını bekleyin"
|
|
50
|
+
};
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
@@ -4,8 +4,11 @@ import { PlatformApiError } from '../platform/errors.js';
|
|
|
4
4
|
import { handle } from '../platform/responses.js';
|
|
5
5
|
import { basename, join, resolve } from 'node:path';
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
import { evaluatePolicy, enforcePolicy } from '../knowledge/policy.js';
|
|
8
|
+
import { policyRules } from '../knowledge/rules.js';
|
|
9
|
+
import { EXTERNAL_COMPONENT_PROTOCOL_VERSION } from '../knowledge/backendConstraints.js';
|
|
10
|
+
/** Platformun köprü protokolü; scaffold ve publish bu sürüme göre üretir/doğrular (backend ExternalComponentRules.CurrentProtocolVersion) */
|
|
11
|
+
const PROTOCOL_VERSION = EXTERNAL_COMPONENT_PROTOCOL_VERSION;
|
|
9
12
|
/** Backend `ExternalComponentRules.MaxBundleSizeBytes` ile aynı sınır */
|
|
10
13
|
const maxBundleBytes = 2 * 1024 * 1024;
|
|
11
14
|
const slugPattern = /^x-[a-z][a-z0-9-]*$/;
|
|
@@ -185,7 +188,7 @@ export function registerExternalComponentTools(server, platform) {
|
|
|
185
188
|
}));
|
|
186
189
|
server.registerTool('external_component_publish', {
|
|
187
190
|
title: 'Harici komponenti yayınla',
|
|
188
|
-
description: "Komponent kaydını ve paketini platforma yükler. Paket sunucudaki doğrulama hattından geçer (şema + güvenlik taraması); block bulgusu varsa depolamaya hiç yazılmaz ve rapor döner. Kabul edilen paket taslak (draft) olarak kaydedilir — Studio önizlemesinde görünür, yayınlanmış uygulamada görünmez; yayına almak için external_component_activate. Dosyalar gateway sürecinin okuyabildiği yerel yollardır, içerikleri model bağlamına girmez.",
|
|
191
|
+
description: "Komponent kaydını ve paketini platforma yükler. Paket sunucudaki doğrulama hattından geçer (şema + güvenlik taraması); block bulgusu varsa depolamaya hiç yazılmaz ve rapor döner. Kabul edilen paket taslak (draft) olarak kaydedilir — Studio önizlemesinde görünür, yayınlanmış uygulamada görünmez; yayına almak için external_component_activate. Dosyalar gateway sürecinin okuyabildiği yerel yollardır, içerikleri model bağlamına girmez. Backend ön kontrolü (EXT-001, block): protocolVersion 1, definition.blocks dolu, props[].name ayrılmış DOM adı ya da zx/on önekli olamaz, props[].type sözlükten (ngapps://rules/backend-constraints).",
|
|
189
192
|
inputSchema: {
|
|
190
193
|
project: projectParam,
|
|
191
194
|
environment: environmentParam,
|
|
@@ -219,6 +222,9 @@ export function registerExternalComponentTools(server, platform) {
|
|
|
219
222
|
if (bytes.byteLength > maxBundleBytes) {
|
|
220
223
|
throw new PlatformApiError(`external_component_publish: paket ${maxBundleBytes} bayt sınırını aşıyor (${bytes.byteLength} B).`);
|
|
221
224
|
}
|
|
225
|
+
// EXT-001: protocolVersion, definition.blocks, ayrılmış prop adları ve prop tipleri — kayıt
|
|
226
|
+
// ucu 422 dönmeden manifest'te yakalanır; paket yükleme ikinci istek olduğu için yarım kayıt kalmaz.
|
|
227
|
+
enforcePolicy(evaluatePolicy(config, policyRules, 'externalComponent', { ...manifest, slug }, { operation: 'create' }));
|
|
222
228
|
const record = await platform.tenant('POST', project, `/components/${applicationId}/external`, {
|
|
223
229
|
environment,
|
|
224
230
|
body: {
|
package/dist/tools/files.js
CHANGED
|
@@ -4,9 +4,12 @@ import { handle } from '../platform/responses.js';
|
|
|
4
4
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
5
5
|
import { basename, extname, resolve } from 'node:path';
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
+
import { FORBIDDEN_NAME_CHARACTERS } from '../knowledge/backendConstraints.js';
|
|
7
8
|
const maxInlineBytes = 8 * 1024 * 1024;
|
|
8
9
|
const maxUploadBytes = 50 * 1024 * 1024;
|
|
9
|
-
|
|
10
|
+
/** Metin içerik bağlamdan geçtiği için base64'ün 8 MB sınırından çok daha dar tutulur (G-26). */
|
|
11
|
+
const maxTextBytes = 1024 * 1024;
|
|
12
|
+
const forbiddenNameChars = FORBIDDEN_NAME_CHARACTERS;
|
|
10
13
|
const mimeTypes = {
|
|
11
14
|
'.css': 'text/css',
|
|
12
15
|
'.csv': 'text/csv',
|
|
@@ -35,6 +38,56 @@ const mimeTypes = {
|
|
|
35
38
|
'.xml': 'application/xml',
|
|
36
39
|
'.zip': 'application/zip'
|
|
37
40
|
};
|
|
41
|
+
export async function uploadFile(platform, { project, environment, path, content, text, filename, contentType, folderId }) {
|
|
42
|
+
const given = [path, content, text].filter(value => value !== undefined);
|
|
43
|
+
if (given.length !== 1) {
|
|
44
|
+
throw new PlatformApiError("file_upload: `path`, `content` ya da `text` parametrelerinden tam olarak biri verilmeli — `path` gateway'in okuyabildiği yerel dosya, `content` base64 içerik, `text` düz metin (ikisi de `filename` ile).");
|
|
45
|
+
}
|
|
46
|
+
let bytes;
|
|
47
|
+
let name;
|
|
48
|
+
if (path) {
|
|
49
|
+
const absolute = resolve(path);
|
|
50
|
+
try {
|
|
51
|
+
bytes = await readFile(absolute);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
throw new PlatformApiError(`file_upload: dosya okunamadı — ${absolute} (${error instanceof Error ? error.message : String(error)}). Yol gateway sürecinin çalıştığı makineye göre çözülür.`);
|
|
55
|
+
}
|
|
56
|
+
name = filename ?? basename(absolute);
|
|
57
|
+
}
|
|
58
|
+
else if (text !== undefined) {
|
|
59
|
+
if (!filename)
|
|
60
|
+
throw new PlatformApiError('file_upload: `text` verildiğinde `filename` zorunludur.');
|
|
61
|
+
bytes = Buffer.from(text, 'utf8');
|
|
62
|
+
if (bytes.byteLength > maxTextBytes) {
|
|
63
|
+
throw new PlatformApiError(`file_upload: \`text\` içeriği ${maxTextBytes} bayt sınırını aşıyor (${bytes.byteLength} B). Büyük dosyalar için \`path\` ya da \`content\` kullanın.`);
|
|
64
|
+
}
|
|
65
|
+
name = filename;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
if (!filename)
|
|
69
|
+
throw new PlatformApiError('file_upload: `content` verildiğinde `filename` zorunludur.');
|
|
70
|
+
bytes = Buffer.from(content, 'base64');
|
|
71
|
+
if (bytes.byteLength === 0)
|
|
72
|
+
throw new PlatformApiError('file_upload: `content` geçerli base64 değil ya da boş.');
|
|
73
|
+
if (bytes.byteLength > maxInlineBytes) {
|
|
74
|
+
throw new PlatformApiError(`file_upload: base64 içerik ${maxInlineBytes} bayt sınırını aşıyor (${bytes.byteLength} B). Büyük dosyalar için \`path\` kullanın.`);
|
|
75
|
+
}
|
|
76
|
+
name = filename;
|
|
77
|
+
}
|
|
78
|
+
if (bytes.byteLength > maxUploadBytes) {
|
|
79
|
+
throw new PlatformApiError(`file_upload: dosya ${maxUploadBytes} bayt platform sınırını aşıyor (${bytes.byteLength} B).`);
|
|
80
|
+
}
|
|
81
|
+
const offendingChar = [...forbiddenNameChars].find(character => name.includes(character));
|
|
82
|
+
if (offendingChar) {
|
|
83
|
+
throw new PlatformApiError(`file_upload: dosya adı '${offendingChar}' karakterini içeriyor; platform doğrulaması bunu 422 ile reddeder (FILE-001, UploadFileCommandValidator). Yasak karakterler: ${forbiddenNameChars}`);
|
|
84
|
+
}
|
|
85
|
+
const resolvedType = contentType ?? mimeTypes[extname(name).toLowerCase()] ?? 'application/octet-stream';
|
|
86
|
+
const multipart = folderId
|
|
87
|
+
? [{ name: 'FolderId', value: folderId }, { name: 'File', filename: name, contentType: resolvedType, content: bytes }]
|
|
88
|
+
: [{ name: 'File', filename: name, contentType: resolvedType, content: bytes }];
|
|
89
|
+
return platform.tenant('POST', project, '/files/upload', { environment, multipart });
|
|
90
|
+
}
|
|
38
91
|
export function registerFileTools(server, platform) {
|
|
39
92
|
const config = platform.config;
|
|
40
93
|
server.registerTool('file_list', {
|
|
@@ -51,57 +104,19 @@ export function registerFileTools(server, platform) {
|
|
|
51
104
|
}, handle(config, ({ project, environment, id }) => platform.tenant('GET', project, `/files/${id}`, { environment })));
|
|
52
105
|
server.registerTool('file_upload', {
|
|
53
106
|
title: 'Dosya yükle',
|
|
54
|
-
description: "Dosyayı projenin depolamasına yükler ve FileDto (id + serve adresleri) döner. İçerik
|
|
107
|
+
description: "Dosyayı projenin depolamasına yükler ve FileDto (id + serve adresleri) döner. İçerik ÜÇ yoldan verilir: `text` — düz metin içerik (SVG, CSV, Markdown, JSON…) + `filename`; base64'e çevirmeye gerek yok, bunu gateway yapar. `path` — gateway sürecinin okuyabildiği yerel yol (büyük ve ikili dosyalar için). `content` — base64 (8 MB'a kadar) + `filename`. Platform sınırı 50 MB; dosya adı \\|!#~$%&/=?»«@£§€{};'<>, karakterlerini içeremez. Yüklenen dosya sayfada `image`/`file-viewer` komponentiyle gösterilir ya da satır alanına yol olarak yazılır.",
|
|
55
108
|
inputSchema: {
|
|
56
109
|
project: projectParam,
|
|
57
110
|
environment: environmentParam,
|
|
58
111
|
path: z.string().min(1).optional().describe("Gateway sürecinin okuyabildiği yerel dosya yolu; `content` ile birlikte verilmez"),
|
|
59
112
|
content: z.string().min(1).optional().describe('Base64 kodlanmış dosya içeriği; `filename` ile birlikte zorunludur'),
|
|
113
|
+
text: z.string().min(1).optional().describe("Düz metin içerik (UTF-8): SVG, CSV, Markdown, JSON, düz metin. `filename` ile birlikte zorunludur ve base64'e çevirmeye gerek yoktur — yalnız MCP araçlarıyla çalışan bir ajanın elinde base64 kodlayıcı yok, bu alan o boşluğu kapatır (G-26). İkili dosyalar için `path` ya da `content` kullanın."),
|
|
60
114
|
filename: z.string().min(1).optional().describe("`content` ile zorunlu; `path` verildiğinde dosya adını ezer"),
|
|
61
115
|
contentType: z.string().min(1).optional().describe('Belirtilmezse uzantıdan çözülür, bilinmeyen uzantıda application/octet-stream'),
|
|
62
116
|
folderId: guid.optional().describe('Hedef klasör (folder_list); boşsa kök')
|
|
63
117
|
},
|
|
64
118
|
annotations: writes
|
|
65
|
-
}, handle(config,
|
|
66
|
-
if (!path === !content) {
|
|
67
|
-
throw new PlatformApiError("file_upload: `path` ya da `content` parametrelerinden tam olarak biri verilmeli — `path` gateway'in okuyabildiği yerel dosya, `content` base64 içerik (`filename` ile).");
|
|
68
|
-
}
|
|
69
|
-
let bytes;
|
|
70
|
-
let name;
|
|
71
|
-
if (path) {
|
|
72
|
-
const absolute = resolve(path);
|
|
73
|
-
try {
|
|
74
|
-
bytes = await readFile(absolute);
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
throw new PlatformApiError(`file_upload: dosya okunamadı — ${absolute} (${error instanceof Error ? error.message : String(error)}). Yol gateway sürecinin çalıştığı makineye göre çözülür.`);
|
|
78
|
-
}
|
|
79
|
-
name = filename ?? basename(absolute);
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
if (!filename)
|
|
83
|
-
throw new PlatformApiError('file_upload: `content` verildiğinde `filename` zorunludur.');
|
|
84
|
-
bytes = Buffer.from(content, 'base64');
|
|
85
|
-
if (bytes.byteLength === 0)
|
|
86
|
-
throw new PlatformApiError('file_upload: `content` geçerli base64 değil ya da boş.');
|
|
87
|
-
if (bytes.byteLength > maxInlineBytes) {
|
|
88
|
-
throw new PlatformApiError(`file_upload: base64 içerik ${maxInlineBytes} bayt sınırını aşıyor (${bytes.byteLength} B). Büyük dosyalar için \`path\` kullanın.`);
|
|
89
|
-
}
|
|
90
|
-
name = filename;
|
|
91
|
-
}
|
|
92
|
-
if (bytes.byteLength > maxUploadBytes) {
|
|
93
|
-
throw new PlatformApiError(`file_upload: dosya ${maxUploadBytes} bayt platform sınırını aşıyor (${bytes.byteLength} B).`);
|
|
94
|
-
}
|
|
95
|
-
const offendingChar = [...forbiddenNameChars].find(character => name.includes(character));
|
|
96
|
-
if (offendingChar) {
|
|
97
|
-
throw new PlatformApiError(`file_upload: dosya adı '${offendingChar}' karakterini içeriyor; platform doğrulaması bunu reddeder. Yasak karakterler: ${forbiddenNameChars}`);
|
|
98
|
-
}
|
|
99
|
-
const resolvedType = contentType ?? mimeTypes[extname(name).toLowerCase()] ?? 'application/octet-stream';
|
|
100
|
-
const multipart = folderId
|
|
101
|
-
? [{ name: 'FolderId', value: folderId }, { name: 'File', filename: name, contentType: resolvedType, content: bytes }]
|
|
102
|
-
: [{ name: 'File', filename: name, contentType: resolvedType, content: bytes }];
|
|
103
|
-
return platform.tenant('POST', project, '/files/upload', { environment, multipart });
|
|
104
|
-
}));
|
|
119
|
+
}, handle(config, (input) => uploadFile(platform, input)));
|
|
105
120
|
server.registerTool('file_download', {
|
|
106
121
|
title: 'Dosya indir',
|
|
107
122
|
description: "Dosyanın içeriğini indirir ve `outPath` ile verilen yerel yola yazar; dönen gövde yalnız özet (yol, boyut, tip) — ikili içerik model bağlamına girmez. Yol gateway sürecinin çalıştığı makineye göre çözülür.",
|