@appaflytech/zeyrek-ai-mcp 0.0.6 → 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 +8 -0
- package/README.md +9 -8
- package/dist/config.js +18 -3
- package/dist/knowledge/backendConstraints.js +85 -0
- package/dist/knowledge/componentCatalog.js +357 -54
- package/dist/knowledge/componentEvents.js +120 -25
- package/dist/knowledge/functionGuards.js +134 -0
- package/dist/knowledge/normalize.js +53 -2
- package/dist/knowledge/outlines.js +31 -0
- package/dist/knowledge/policy.js +14 -5
- package/dist/knowledge/rules.js +343 -1
- package/dist/platform/csv.js +123 -0
- package/dist/platform/errors.js +62 -3
- package/dist/platform/http.js +1 -0
- package/dist/platform/responses.js +16 -4
- package/dist/platform/screenshot.js +18 -1
- package/dist/resources/examples/flow-call-function.json +66 -0
- package/dist/resources/examples/function-total.json +70 -0
- package/dist/resources/examples/microfunction-node-sum.json +56 -0
- package/dist/resources/examples/page-dynamic-form.json +1 -1
- package/dist/resources/examples/page-sign-in.json +1 -1
- package/dist/resources/guides/component.md +13 -1
- package/dist/resources/guides/entity.md +321 -17
- package/dist/resources/guides/expressions.md +3 -0
- package/dist/resources/guides/functions.md +262 -0
- package/dist/resources/guides/getting-started.md +5 -0
- package/dist/resources/guides/merge.md +231 -0
- package/dist/resources/guides/microfunction.md +166 -0
- package/dist/resources/guides/microservice.md +61 -2
- package/dist/resources/guides/modify-existing.md +23 -0
- package/dist/resources/guides/page.md +20 -4
- package/dist/resources/guides/translation.md +24 -0
- package/dist/resources/guides/workflow.md +1 -1
- package/dist/resources/schema-fingerprint.json +4 -4
- package/dist/resources/schemas/AnalyzeImportQuery.json +51 -0
- package/dist/resources/schemas/ApplyMergePlanCommand.json +19 -0
- package/dist/resources/schemas/CreateBulkRowCommand.json +3 -0
- 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 +119 -1
- package/dist/resources/schemas/CreateWorkflowCommand.json +13 -2
- 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 +3 -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 +119 -1
- package/dist/resources/schemas/PreviewWorkflowCommand.json +13 -2
- 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 +13 -2
- package/dist/resources/schemas/UpdateBulkRowCommand.json +3 -0
- package/dist/resources/schemas/UpdateEntityCommand.json +3 -1
- 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 +119 -1
- package/dist/resources/schemas/UpdateWorkflowCommand.json +13 -2
- package/dist/server.js +15 -3
- package/dist/tools/activities.js +40 -0
- package/dist/tools/automations.js +14 -5
- package/dist/tools/common.js +16 -0
- package/dist/tools/dependencies.js +1 -1
- package/dist/tools/environments.js +52 -0
- package/dist/tools/files.js +51 -50
- package/dist/tools/flows.js +95 -2
- package/dist/tools/functions.js +331 -0
- package/dist/tools/importSchemas.js +79 -0
- package/dist/tools/imports.js +241 -0
- package/dist/tools/merges.js +113 -0
- package/dist/tools/microfunctions.js +155 -0
- package/dist/tools/microservices.js +42 -3
- package/dist/tools/pages.js +62 -10
- package/dist/tools/platformInfo.js +4 -1
- package/dist/tools/revisions.js +94 -0
- package/dist/tools/rows.js +342 -22
- package/dist/tools/screenWrite.js +11 -2
- package/dist/tools/templates.js +32 -5
- package/dist/tools/workflows.js +24 -6
- package/package.json +3 -1
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { guid, projectParam, environmentParam, pagingParams, readOnly, writes, destructive } from './common.js';
|
|
2
|
+
import { importChoicesShape, importChoicesSchema, importErrorPolicies, importRowActions, validateChoices } from './importSchemas.js';
|
|
3
|
+
import { PlatformApiError } from '../platform/errors.js';
|
|
4
|
+
import { handle } from '../platform/responses.js';
|
|
5
|
+
import { uploadFile } from './files.js';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
const localeParam = z.enum(['tr-TR', 'en-US']).optional()
|
|
8
|
+
.describe("Sayı, tarih ve Evet/Hayır yazımını çözen yerel ayar profili. Verilmezse backend proje dilinden seçer. Aynı dosya 'en-US' ile okunduğunda '15.03.2024' ve '42.500,00' metne düşer — bu kayıp değil, kayıpsızlık kuralıdır.");
|
|
9
|
+
const csvParam = z.object({
|
|
10
|
+
delimiter: z.string().min(1).optional().describe("Sütun ayracı; verilmezse ilk 20 satırdan tespit edilir (';' ',' tab '|')"),
|
|
11
|
+
encoding: z.string().min(1).optional().describe("Kodlama; verilmezse BOM'dan ya da UTF-8 denemesinden tespit edilir, başarısızsa windows-1254")
|
|
12
|
+
}).optional().describe('Yalnız CSV için; tespit yanlışsa buradan ezilir');
|
|
13
|
+
function asArray(value) {
|
|
14
|
+
return Array.isArray(value) ? value : [];
|
|
15
|
+
}
|
|
16
|
+
function candidateNote(candidate) {
|
|
17
|
+
const columns = asArray(candidate.sourceColumns).join(' → ');
|
|
18
|
+
const target = candidate.target?.existingEntityId
|
|
19
|
+
? 'mevcut tablo'
|
|
20
|
+
: typeof candidate.target?.newTableName === 'string' ? `yeni tablo '${candidate.target.newTableName}'` : 'metin';
|
|
21
|
+
return `${columns} → ${target} (${String(candidate.kind ?? '?')}, öneri: ${String(candidate.recommended ?? '-')})`;
|
|
22
|
+
}
|
|
23
|
+
export function assertNotProduction(tool, environment, config) {
|
|
24
|
+
if ((environment ?? config.defaultEnvironment) !== 'production')
|
|
25
|
+
return;
|
|
26
|
+
throw new PlatformApiError(`${tool}: production'a içe aktarma yapılmaz; geri alma ve iptal de gönderilmez — hedef environment 'development' ya da bir dal olmalı. Production'a veri taşımak insan onaylı deployment işidir.`, 400);
|
|
27
|
+
}
|
|
28
|
+
export function registerImportTools(server, platform) {
|
|
29
|
+
const config = platform.config;
|
|
30
|
+
server.registerTool('import_analyze', {
|
|
31
|
+
title: 'Excel/CSV/JSON dosyasını analiz et',
|
|
32
|
+
description: "Yüklenmiş bir xlsx/csv/json dosyasının sayfalarını, sütunlarını, çıkarılan tiplerini, ilişki adaylarını ve ilk satırlarını tek yanıtta döner. JSON kökü dört biçimden biridir: nesne dizisi (tek sayfa), tek nesne (tek satır), her özelliği nesne dizisi olan nesne (özellik başına bir sayfa) ve row_export'un { meta, data } gövdesi (meta okunmaz, veri tek sayfa olur); okunamayan dosya gerekçesiyle 400 alır. Satırı gövdede taşımaz — 300 satırlık bir tabloyu row_create ile elle yazmak yerine dosyayı `path` ile verin, gateway önce yükler. Zincir: import_analyze → import_plan → import_run → import_status. Hiçbir satır yazılmaz, bu bir sorgudur.",
|
|
33
|
+
inputSchema: {
|
|
34
|
+
project: projectParam,
|
|
35
|
+
environment: environmentParam,
|
|
36
|
+
datasourceId: guid.describe('Tabloların açılacağı datasource (datasource_list; isBuiltIn olan projenin kendi veritabanıdır)'),
|
|
37
|
+
fileId: guid.optional().describe('Daha önce file_upload ile yüklenmiş dosya; `path` ile birlikte verilmez'),
|
|
38
|
+
path: z.string().min(1).optional().describe("Gateway sürecinin okuyabildiği yerel dosya yolu; gateway önce file_upload'ı çağırır ve dönen fileId'yi yanıta ekler"),
|
|
39
|
+
locale: localeParam,
|
|
40
|
+
headerRow: z.number().int().min(0).optional().describe("Başlık satırının 1 tabanlı numarası; 0 = başlık yok, varsayılan 1. JSON'da yok sayılır: başlık anahtarlardır ve her zaman 1 olur"),
|
|
41
|
+
csv: csvParam
|
|
42
|
+
},
|
|
43
|
+
annotations: readOnly
|
|
44
|
+
}, handle(config, async ({ project, environment, datasourceId, fileId, path, locale, headerRow, csv }) => {
|
|
45
|
+
if ((fileId === undefined) === (path === undefined)) {
|
|
46
|
+
throw new PlatformApiError('import_analyze: `fileId` ya da `path` parametrelerinden tam olarak biri verilmeli — `path` gateway sürecinin okuyabildiği yerel dosya, `fileId` daha önce file_upload ile yüklenmiş dosya.');
|
|
47
|
+
}
|
|
48
|
+
const notes = [];
|
|
49
|
+
let resolvedFileId = fileId;
|
|
50
|
+
if (path !== undefined) {
|
|
51
|
+
const uploaded = await uploadFile(platform, { project, environment, path });
|
|
52
|
+
const uploadedFile = uploaded?.data;
|
|
53
|
+
if (typeof uploadedFile?.id !== 'string') {
|
|
54
|
+
throw new PlatformApiError(`import_analyze: dosya yüklendi ama yanıtta fileId yok (${path}).`);
|
|
55
|
+
}
|
|
56
|
+
resolvedFileId = uploadedFile.id;
|
|
57
|
+
notes.push(`Dosya yüklendi: fileId ${resolvedFileId}`);
|
|
58
|
+
}
|
|
59
|
+
const body = {
|
|
60
|
+
fileId: resolvedFileId,
|
|
61
|
+
...(locale === undefined ? {} : { locale }),
|
|
62
|
+
...(headerRow === undefined ? {} : { headerRow }),
|
|
63
|
+
...(csv === undefined ? {} : { csv })
|
|
64
|
+
};
|
|
65
|
+
let payload;
|
|
66
|
+
try {
|
|
67
|
+
payload = await platform.tenant('POST', project, `/entities/${datasourceId}/import/analyze`, { environment, body });
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (error instanceof PlatformApiError && error.code === 'ImportFileUnreadableException') {
|
|
71
|
+
throw new PlatformApiError(`${error.message} CSV ise \`csv.delimiter\` ve \`csv.encoding\` verip yeniden deneyin; .xls dosyasını önce .xlsx olarak kaydedin.`, error.status, error.code);
|
|
72
|
+
}
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
const analysis = payload?.data;
|
|
76
|
+
const sheets = asArray(analysis?.sheets);
|
|
77
|
+
const candidates = sheets.flatMap(sheet => asArray(sheet.relationCandidates));
|
|
78
|
+
const summary = {
|
|
79
|
+
sheets: sheets.length,
|
|
80
|
+
rows: sheets.reduce((total, sheet) => total + (typeof sheet.rowCount === 'number' ? sheet.rowCount : 0), 0),
|
|
81
|
+
relationCandidates: candidates.length,
|
|
82
|
+
warnings: asArray(analysis?.warnings).length
|
|
83
|
+
};
|
|
84
|
+
if (candidates.length > 0)
|
|
85
|
+
notes.push(`İlişki adayları: ${candidates.map(candidateNote).join('; ')}`);
|
|
86
|
+
const unconvertible = sheets.flatMap(sheet => asArray(sheet.columns))
|
|
87
|
+
.filter(column => typeof column.inferred?.unconvertible === 'number' && column.inferred.unconvertible > 0);
|
|
88
|
+
if (unconvertible.length > 0) {
|
|
89
|
+
notes.push(`Çevrilemeyen değer taşıyan sütunlar: ${unconvertible.map(column => `${String(column.slug)} (${String(column.inferred?.unconvertible)})`).join(', ')} — tipi ezmek ya da locale değiştirmek için import_plan gövdesinde karar verin.`);
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
...payload,
|
|
93
|
+
summary,
|
|
94
|
+
...(notes.length > 0 ? { note: notes.join(' ') } : {})
|
|
95
|
+
};
|
|
96
|
+
}));
|
|
97
|
+
server.registerTool('import_plan', {
|
|
98
|
+
title: 'İçe aktarma planı (kuru koşu)',
|
|
99
|
+
description: "Analiz çıktısındaki seçimleri alır ve HİÇBİR SATIR YAZMADAN tüm dosyayı doğrular: tip dönüşümü, zorunluluk, benzersizlik, ilişki çözümü, upsert anahtarı. Yanıt planId + sayılar (eklenecek/güncellenecek/değişmeyen) + ilk 100 satır hatası + tam hata raporu dosyasıdır. Plan bir saat geçerlidir. Hataları düzeltmeden import_run çağırmayın.",
|
|
100
|
+
inputSchema: {
|
|
101
|
+
project: projectParam,
|
|
102
|
+
environment: environmentParam,
|
|
103
|
+
datasourceId: guid,
|
|
104
|
+
...importChoicesShape
|
|
105
|
+
},
|
|
106
|
+
annotations: readOnly
|
|
107
|
+
}, handle(config, async ({ project, environment, datasourceId, ...choices }) => {
|
|
108
|
+
assertNotProduction('import_plan', environment, config);
|
|
109
|
+
const problems = validateChoices(importChoicesSchema.parse(choices));
|
|
110
|
+
if (problems.length > 0) {
|
|
111
|
+
throw new PlatformApiError(`import_plan: seçimler eksik — ${problems.join(' ')}`, 400);
|
|
112
|
+
}
|
|
113
|
+
const payload = await platform.tenant('POST', project, `/entities/${datasourceId}/import/plan`, { environment, body: choices });
|
|
114
|
+
const plan = payload?.data;
|
|
115
|
+
const totals = plan?.totals;
|
|
116
|
+
const notes = [];
|
|
117
|
+
if (totals) {
|
|
118
|
+
notes.push(`${String(totals.insert ?? 0)} eklenecek · ${String(totals.update ?? 0)} güncellenecek · ${String(totals.unchanged ?? 0)} değişmeyecek · ${String(totals.errors ?? 0)} hata.`);
|
|
119
|
+
}
|
|
120
|
+
notes.push(plan?.requiresForce === true
|
|
121
|
+
? "Hata oranı %50'nin üstünde: import_run bu planı yalnız force: true ile çalıştırır. Önce errors listesine bakın — çoğu zaman tek bir yanlış locale ya da sütun tipi bütün satırları düşürür."
|
|
122
|
+
: "import_run { planId, errorPolicy } ile başlatın; errorPolicy 'skip-rows' hatalı satırı atlar, 'all-or-nothing' hiçbir şey yazmaz.");
|
|
123
|
+
if (plan?.reportFileId)
|
|
124
|
+
notes.push(`Tam hata listesi dosyada: file_download { id: '${String(plan.reportFileId)}' }.`);
|
|
125
|
+
return { ...payload, note: notes.join(' ') };
|
|
126
|
+
}));
|
|
127
|
+
server.registerTool('import_run', {
|
|
128
|
+
title: 'İçe aktarmayı başlat',
|
|
129
|
+
description: "Kuru koşusu yapılmış planı arka planda çalıştırır ve 202 ile jobId döner. Tablolar kurulur, satırlar 1000'lik partilerle yazılır. Gateway BEKLEMEZ — bitişi import_status ile yoklayın. Yalnız development.",
|
|
130
|
+
inputSchema: {
|
|
131
|
+
project: projectParam,
|
|
132
|
+
environment: environmentParam,
|
|
133
|
+
datasourceId: guid,
|
|
134
|
+
planId: guid.describe('import_plan yanıtındaki planId; bir saat geçerlidir'),
|
|
135
|
+
errorPolicy: z.enum(importErrorPolicies).optional().describe("Planda verilenden farklıysa burada ezilir"),
|
|
136
|
+
rowActions: z.array(z.object({
|
|
137
|
+
row: z.number().int().min(1).describe('Dosyadaki 1 tabanlı satır numarası (başlık dahil)'),
|
|
138
|
+
action: z.enum(importRowActions)
|
|
139
|
+
})).optional().describe("Tek tek satır kararları: 'skip' satırı atlar, 'blank' çevrilemeyen hücreyi boş bırakır, 'apply-suggestion' plandaki öneriyi uygular"),
|
|
140
|
+
force: z.boolean().optional().describe("Hata oranı %50'nin üstündeyken zorunlu")
|
|
141
|
+
},
|
|
142
|
+
annotations: writes
|
|
143
|
+
}, handle(config, async ({ project, environment, datasourceId, ...body }) => {
|
|
144
|
+
assertNotProduction('import_run', environment, config);
|
|
145
|
+
const payload = await platform.tenant('POST', project, `/entities/${datasourceId}/import/run`, { environment, body });
|
|
146
|
+
return {
|
|
147
|
+
...payload,
|
|
148
|
+
note: 'İş arka planda başladı. import_status { jobId } ile izleyin; tamamlanınca result.tables sayıları ve reportFileId gelir. Gateway beklemez, siz tekrar çağırın.'
|
|
149
|
+
};
|
|
150
|
+
}));
|
|
151
|
+
server.registerTool('import_status', {
|
|
152
|
+
title: 'İçe aktarma işinin durumu',
|
|
153
|
+
description: "İş kaydını döner: planned → pending → validating → writing → completed | failed | cancelled; geri alınan içe aktarma undone olur. 'writing' durumunda progress.written/total ilerler; retryAfterSeconds yalnız süren işte gelir. Tamamlanmadan sonuç okumaya çalışmayın.",
|
|
154
|
+
inputSchema: {
|
|
155
|
+
project: projectParam,
|
|
156
|
+
environment: environmentParam,
|
|
157
|
+
jobId: guid.describe('import_run yanıtındaki jobId (plan ile aynı id)')
|
|
158
|
+
},
|
|
159
|
+
annotations: readOnly
|
|
160
|
+
}, handle(config, async ({ project, environment, jobId }) => {
|
|
161
|
+
const payload = await platform.tenant('GET', project, `/datajobs/${jobId}`, { environment });
|
|
162
|
+
const job = payload?.data;
|
|
163
|
+
const status = typeof job?.status === 'string' ? job.status : 'unknown';
|
|
164
|
+
const isImport = job?.kind === 'import';
|
|
165
|
+
if (status === 'completed') {
|
|
166
|
+
const report = job?.result?.reportFileId;
|
|
167
|
+
const notes = [report ? `İş tamamlandı. Atlanan satırların tam listesi dosyada: file_download { id: '${String(report)}' }.` : 'İş tamamlandı.'];
|
|
168
|
+
if (isImport) {
|
|
169
|
+
const undoAvailableUntil = typeof job?.undoAvailableUntil === 'string' ? job.undoAvailableUntil : undefined;
|
|
170
|
+
notes.push(undoAvailableUntil === undefined
|
|
171
|
+
? 'Bu iş geri alınamaz (anlık görüntü yok: mevcut tabloya yalnız ekleme yapıldı ya da 7 günlük pencere kapandı).'
|
|
172
|
+
: Date.parse(undoAvailableUntil) > Date.now()
|
|
173
|
+
? `import_undo { jobId } ile ${undoAvailableUntil} tarihine kadar geri alınabilir.`
|
|
174
|
+
: `Geri alma penceresi ${undoAvailableUntil} tarihinde kapandı; bu iş geri alınamaz.`);
|
|
175
|
+
}
|
|
176
|
+
return { ...payload, note: notes.join(' ') };
|
|
177
|
+
}
|
|
178
|
+
if (status === 'undone') {
|
|
179
|
+
return { ...payload, note: 'Bu içe aktarma geri alındı.' };
|
|
180
|
+
}
|
|
181
|
+
if (status === 'planned') {
|
|
182
|
+
return { ...payload, note: "Plan henüz çalıştırılmadı: import_run { datasourceId, planId } ile başlatın — planId bu jobId'dir ve plan bir saat geçerlidir." };
|
|
183
|
+
}
|
|
184
|
+
if (status === 'failed') {
|
|
185
|
+
return { ...payload, note: `İş düştü; \`error\` alanı nedeni söyler.${isImport ? ' Yazılmış partiler yerinde kalır; bugün geri alınamaz.' : ''}` };
|
|
186
|
+
}
|
|
187
|
+
if (status === 'cancelled') {
|
|
188
|
+
return { ...payload, note: `İş iptal edildi.${isImport ? ' Yazılmış partiler yerinde kalır; bugün geri alınamaz.' : ''}` };
|
|
189
|
+
}
|
|
190
|
+
return { ...payload, retryAfterSeconds: 5, note: `İş sürüyor (${status}). 5 saniye sonra import_status'ü tekrar çağırın; gateway beklemez.` };
|
|
191
|
+
}));
|
|
192
|
+
server.registerTool('import_history', {
|
|
193
|
+
title: 'Veri işleri geçmişi',
|
|
194
|
+
description: "Projedeki içe/dışa aktarma ve geri alma işlerini sayfalı listeler; order verilmezse en yeni iş önce gelir. Her satır kind, status, entityIds ve result taşır. kind: 'import' çalıştırılmamış planları (planned) da içerir; datasource filtresi yoktur. Geri alınabilir bir iş aramak (kind: 'import', status: 'completed', undoAvailableUntil geçmemiş) ya da düşen bir işin nedenini bulmak için kullanılır.",
|
|
195
|
+
inputSchema: {
|
|
196
|
+
project: projectParam,
|
|
197
|
+
environment: environmentParam,
|
|
198
|
+
entityId: guid.optional().describe('Yalnız bu tabloya dokunan işler'),
|
|
199
|
+
kind: z.enum(['import', 'export', 'undo']).optional(),
|
|
200
|
+
...pagingParams
|
|
201
|
+
},
|
|
202
|
+
annotations: readOnly
|
|
203
|
+
}, handle(config, ({ project, environment, order, isDescending, ...query }) => platform.tenant('GET', project, '/datajobs', {
|
|
204
|
+
environment,
|
|
205
|
+
query: { ...query, order: order ?? 'CreatedAt', isDescending: isDescending ?? (order === undefined ? true : undefined) }
|
|
206
|
+
})));
|
|
207
|
+
server.registerTool('import_undo', {
|
|
208
|
+
title: 'İçe aktarmayı geri al',
|
|
209
|
+
description: "Tamamlanmış ve undoAvailableUntil dolu bir içe aktarmayı geri alır: bu işte AÇILAN tablolar (başka tablo bağlı değilse, içindeki bütün satırlarla) ve ilişki için açılan referans satırları silinir; güncellenen satırların eşlenen sütunları önceki değerine döner. Mevcut tabloya EKLENEN satırlar silinmez, çakışma denetimi yoktur. 202 döner; geri alma ayrı bir iştir, import_status ile izlenir ve bitmeden aynı işe tekrar çağrılmaz. Yalnız development; kullanıcıya sormadan çağırmayın.",
|
|
210
|
+
inputSchema: {
|
|
211
|
+
project: projectParam,
|
|
212
|
+
environment: environmentParam,
|
|
213
|
+
jobId: guid.describe('Geri alınacak içe aktarma işinin id\'si (import_run ya da import_history\'den)')
|
|
214
|
+
},
|
|
215
|
+
annotations: destructive
|
|
216
|
+
}, handle(config, async ({ project, environment, jobId }) => {
|
|
217
|
+
assertNotProduction('import_undo', environment, config);
|
|
218
|
+
const payload = await platform.tenant('POST', project, `/datajobs/${jobId}/undo`, { environment });
|
|
219
|
+
return {
|
|
220
|
+
...payload,
|
|
221
|
+
note: "Geri alma işi başladı. import_status { jobId } ile izleyin — dönen jobId GERİ ALMA işinindir, kaynak işin değil; bitene kadar aynı işe import_undo'yu tekrar çağırmayın, ikinci bir geri alma işi açılır. Bittiğinde kaynak iş 'undone' olur: açılan tablolar ve referans satırları silinir, güncellenen satırlar önceki değerine döner, mevcut tabloya eklenen satırlar kalır. Geri alma işinin result.undoSkipped listesi başka tablo bağlı olduğu için silinemeyen tabloları sayar."
|
|
222
|
+
};
|
|
223
|
+
}));
|
|
224
|
+
server.registerTool('import_cancel', {
|
|
225
|
+
title: 'İçe aktarma işini iptal et',
|
|
226
|
+
description: "Bir içe aktarma işini durdurur: planned, pending ya da validating iş hemen ve kesin durur (yazmadan önce açılan tablo ve referans satırları kalır), writing iş o partinin sonunda durur. İptal ile tamamlanma yarışırsa önce yazan kazanır: iş bittiyse 409 döner ve completed kalır. Bitmiş işe (completed, failed, undone, cancelled) 409; tamamlanmış işi geri almak için import_undo. Yalnız development.",
|
|
227
|
+
inputSchema: {
|
|
228
|
+
project: projectParam,
|
|
229
|
+
environment: environmentParam,
|
|
230
|
+
jobId: guid.describe('İptal edilecek işin id\'si (import_run ya da import_history\'den)')
|
|
231
|
+
},
|
|
232
|
+
annotations: writes
|
|
233
|
+
}, handle(config, async ({ project, environment, jobId }) => {
|
|
234
|
+
assertNotProduction('import_cancel', environment, config);
|
|
235
|
+
const payload = await platform.tenant('POST', project, `/datajobs/${jobId}/cancel`, { environment });
|
|
236
|
+
return {
|
|
237
|
+
...payload,
|
|
238
|
+
note: "writing işte iptal parti sonunda etkili olur; import_status { jobId } ile progress.written'ın durduğunu görün. pending ya da validating iş hiç satır yazmadan durur. İptalden önce yazılan partiler yerinde kalır ve bugün geri alınamaz."
|
|
239
|
+
};
|
|
240
|
+
}));
|
|
241
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { destructive, guid, pagingParams, readOnly, updates, writes } from './common.js';
|
|
2
|
+
import { handle } from '../platform/responses.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
const mergeView = z.enum(['summary', 'conflicts', 'full']).optional()
|
|
5
|
+
.describe("Planın ne kadarı dönsün: summary yalnız sayaçlar, conflicts yalnız çakışan kayıt/elemanlar (varsayılan), full her değişiklik");
|
|
6
|
+
const mergeStatus = z.enum(['planning', 'planned', 'applying', 'applied', 'applied-with-drift', 'reverted', 'failed', 'discarded']).optional()
|
|
7
|
+
.describe("Duruma göre süz; 'applied' uygulanmış merge'ler, 'reverted' sonradan geri alınmış olanlar, 'discarded' bırakılmış planlar");
|
|
8
|
+
const mergeTitle = z.string().trim().min(1).max(100)
|
|
9
|
+
.describe("İsteğin adı — git’teki merge request başlığı gibi: dal ne getiriyor. Kalıp: '<dal> → development: <amaç>'. ZORUNLUDUR (backend 2026-09-10’dan beri başlıksız isteği 400 ile reddeder) ve istekle birlikte kalıcıdır; geçmişte bu adla görünür.");
|
|
10
|
+
const mergeDescription = z.string().trim().max(2000).optional()
|
|
11
|
+
.describe("Dalda ne değişti ve neden merge edilmeli — gözden geçirene yazılan gövde. Merge bunu ASLA ezmez; opsiyoneldir ama açıklamasız istekte değişiklik listesi okunacak tek şey kalır.");
|
|
12
|
+
const resolution = z.object({
|
|
13
|
+
itemKey: z.string().min(1).describe("Plan kaydının anahtarı, ör. 'page:8f1c…' (merge_get çıktısındaki items[].key)"),
|
|
14
|
+
path: z.string().optional().describe("Eleman yolu, ör. 'components[elementId=btn_save].props[name=label].ref'; boş bırakılırsa o kaydın TÜM çakışmaları çözülür"),
|
|
15
|
+
choice: z.enum(['source', 'target']).describe("source = dal, target = development")
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* B-08 (2026-09-09 koşusu): `ResolveMergeConflictsCommandHandler` yanıtı
|
|
19
|
+
* `MergePlanDto.Map(..., view: Full)` ile kuruyor — yani her çözüm çağrısı planın TAMAMINI geri
|
|
20
|
+
* döndürüyor. Orta boy bir planda yanıt 68-121 KB'a çıkıp gateway yanıt sınırını aşıyor; çağrı
|
|
21
|
+
* hata gibi görünüyor, oysa çözüm uygulanmış oluyor (koşuda üç kez tekrarlandı). Gateway gövdeyi
|
|
22
|
+
* sayaçlara indirir; planın kendisini isteyen `merge_get` çağırır.
|
|
23
|
+
*/
|
|
24
|
+
export function resolveSummary(payload, planId, requested) {
|
|
25
|
+
const envelope = (payload && typeof payload === 'object' ? payload : {});
|
|
26
|
+
const plan = ((envelope.data && typeof envelope.data === 'object' ? envelope.data : envelope) ?? {});
|
|
27
|
+
const resolvedElements = (plan.items ?? []).reduce((total, item) => total + (item?.elements ?? []).filter(element => element?.resolution !== undefined && element?.resolution !== null).length, 0);
|
|
28
|
+
return {
|
|
29
|
+
planId: plan.id ?? planId,
|
|
30
|
+
...(plan.status === undefined ? {} : { status: plan.status }),
|
|
31
|
+
...(plan.summary === undefined ? {} : { summary: plan.summary }),
|
|
32
|
+
resolved: { requested, elements: resolvedElements },
|
|
33
|
+
note: "Yanıt ÖZETTİR — plan gövdesi kesildi. Planın kalan çakışmaları için merge_get { projectId, planId, view: 'conflicts' }; summary.unresolved 0 olduğunda merge_apply."
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function registerMergeTools(server, platform) {
|
|
37
|
+
const config = platform.config;
|
|
38
|
+
server.registerTool('merge_plan', {
|
|
39
|
+
title: 'Merge planı oluştur',
|
|
40
|
+
description: "Bir dalı ebeveynine (development) merge etmek için üç yönlü diff planı ister. Hedef her zaman dalın ebeveynidir — production'a merge eden bir yol YOKTUR. title ZORUNLUDUR: isteğin adıdır ve geçmişte o adla durur. Asenkron: yanıt 202 döner, plan hemen dolu değildir. Projede açık bir plan varsa 400 döner; önce onu uygulayın ya da merge_discard ile bırakın.",
|
|
41
|
+
inputSchema: { projectId: guid, sourceEnvironmentId: guid, title: mergeTitle, description: mergeDescription },
|
|
42
|
+
annotations: writes
|
|
43
|
+
}, handle(config, async ({ projectId, sourceEnvironmentId, title, description }) => {
|
|
44
|
+
const payload = await platform.org('POST', `/merges/${projectId}`, { body: { sourceEnvironmentId, title, description } });
|
|
45
|
+
return {
|
|
46
|
+
...(payload && typeof payload === 'object' ? payload : { data: payload }),
|
|
47
|
+
note: "plan asenkron hesaplanıyor; merge_get ile status=planned bekleyin"
|
|
48
|
+
};
|
|
49
|
+
}));
|
|
50
|
+
server.registerTool('merge_list', {
|
|
51
|
+
title: 'Merge geçmişini listele',
|
|
52
|
+
description: "Projenin merge planlarını en yeniden eskiye listeler — kim, ne zaman, hangi başlıkla merge etti. items[] bu uçta HER ZAMAN boş döner; bir planın değişiklik listesi için merge_get kullanın. status='applied' uygulanmış merge'leri verir (title, description, createdBy, appliedBy, appliedAt, summary sayaçları); 'discarded' bırakılmış, 'failed' başarısız planlardır. environmentId kaynak ya da hedef olarak o environment'ı kullanan planları süzer. Her satırda kind vardır (merge | revert); bir geri alma planında revertsPlanId geri alınan planı gösterir, o plan da reverted durumundadır.",
|
|
53
|
+
inputSchema: { projectId: guid, status: mergeStatus, environmentId: guid.optional(), ...pagingParams },
|
|
54
|
+
annotations: readOnly
|
|
55
|
+
}, handle(config, ({ projectId, ...query }) => platform.org('GET', `/merges/${projectId}`, { query })));
|
|
56
|
+
server.registerTool('merge_get', {
|
|
57
|
+
title: 'Merge planını oku',
|
|
58
|
+
description: "Bir merge planını döner. items[] yalnız DEĞİŞEN kayıtları taşır; her kayıtta kind (add|update|delete|conflict) ve elements[] vardır. Çakışan elemanlarda base/source/target üç değeri birden gelir. dataRisk: true olan kalemde SEBEP dataRiskReasons[] içindedir: 'field-removed' (alan siliniyor), 'field-narrowed' (tip/nullable/unique daraltılıyor), 'not-null-without-default-on-populated-table' (hedefte satır varken varsayılansız NOT NULL alan ekleniyor — bu apply'ı DDL aşamasında düşürür, alanı nullable yapın ya da varsayılan verin). dataRisk bir UYARIDIR, apply'ı bloklamaz. derivedFromKey dolu olan kalem OTOMATİK ÜRETİLMİŞ bir kayıttır (varsayılan DTO); kaynağını (entity ya da query) çözdüğünüzde aynı yoldaki çakışması aynı tarafa KENDİLİĞİNDEN çözülür — ayrı ayrı ve zıt çözülemez. Uygulanmış planlarda title, description, createdBy ve appliedBy da gelir. kind=revert ise bu bir geri alma planıdır: revertsPlanId geri aldığı planı verir ve elements'te source merge ÖNCESİ, target ŞİMDİKİ değerdir. isStale: true ise plan alındığından beri DAL ya da development değişmiştir: items o değişikliği taşimaz ve merge_apply 409 döner — merge_refresh ile planı tazeleyin. elements[].isNoise: true olan eleman GÖSTERİM gürültüsüdür — kanvasın kendi durumu (workflow schema), DTO'dan türetilen şema (microservice schema.parameterScheme/returnScheme), sunucu yönetimli anahtar; merge gövdesinde KALIR ve uygulanır, yalnız okurken atlanır. Çakışan eleman asla işaretlenmez. items[].references[] o kalemin gövdesindeki guid'lerin karşılığıdır ({ id, name, module }): bir table step'in rule.entityId'si hangi tabloya, bir branch'in nextStepId'si hangi adıma, bir filtrenin fieldId'si hangi alana işaret ediyor — plan dışına çıkmadan okunur. items[].parentKey dolu olan kayıt başka bir kaydın çocuğudur (DTO çocukları, iç içe klasörler); ebeveyniyle birlikte okunur. items[].recordType kaydın modül içindeki türüdür — bir workflow kaydı 'workflow', 'automation' ya da 'middleware' olabilir ve modül üçüne de workflow der. items[].hiddenFields o kaydın GÖVDESİNDE okunmayacak anahtarlardır (aynı politika; eklenen/silinen kayıt tek eleman olarak geldiği için yolun değil anahtarın sorusudur).",
|
|
59
|
+
inputSchema: { projectId: guid, planId: guid, view: mergeView },
|
|
60
|
+
annotations: readOnly
|
|
61
|
+
}, handle(config, ({ projectId, planId, view }) => platform.org('GET', `/merges/${projectId}/${planId}?view=${view ?? 'conflicts'}`)));
|
|
62
|
+
server.registerTool('merge_resolve', {
|
|
63
|
+
title: 'Merge çakışmalarını çöz',
|
|
64
|
+
description: "Çakışan elemanlar için dal (source) ya da development (target) seçimini plana yazar. Yalnız status=planned iken çalışır. path boş bırakılırsa o kaydın tüm çakışmaları aynı tarafa çözülür. YANIT ÖZETTİR: { planId, status, summary, resolved } — platform planın tamamını döner ama gateway onu kesip yalnız sayaçları verir (orta boy planda tam gövde 68-121 KB'a çıkıyor ve yanıt sınırını aşıyordu; çağrı hata gibi görünürken çözüm UYGULANMIŞ oluyordu). Planın kendisini görmek için merge_get { planId, view:'conflicts' } çağırın; kalan çakışma sayısı summary.unresolved'dadır.",
|
|
65
|
+
inputSchema: { projectId: guid, planId: guid, resolutions: z.array(resolution).min(1) },
|
|
66
|
+
annotations: updates
|
|
67
|
+
}, handle(config, async ({ projectId, planId, resolutions }) => {
|
|
68
|
+
const payload = await platform.org('PUT', `/merges/${projectId}/${planId}/resolutions`, { body: { resolutions } });
|
|
69
|
+
return resolveSummary(payload, planId, resolutions.length);
|
|
70
|
+
}));
|
|
71
|
+
server.registerTool('merge_apply', {
|
|
72
|
+
title: 'Merge planını uygula',
|
|
73
|
+
description: "Planı tek transaction'da development'a uygular ve dalın şemasını düşürür. İsteğin adı zaten plan açılırken verilir; apply ayrı bir mesaj almaz. Asenkron: yanıt 202 döner. Çözülmemiş çakışma varsa 409 döner. PLAN BAYATSA DA 409 döner: plan alındıktan sonra DAL ya da development değiştiyse apply reddedilir — plan dalın o anki fotoğrafını taşır, uygulansaydı sonradan yapılan iş sessizce düşerdi. Çözümü merge_refresh'tir; yeni plan açmanız gerekmez.",
|
|
74
|
+
inputSchema: { projectId: guid, planId: guid },
|
|
75
|
+
annotations: writes
|
|
76
|
+
}, handle(config, async ({ projectId, planId }) => {
|
|
77
|
+
const payload = await platform.org('POST', `/merges/${projectId}/${planId}/apply`, { body: { projectId, planId } });
|
|
78
|
+
return {
|
|
79
|
+
...(payload && typeof payload === 'object' ? payload : { data: payload }),
|
|
80
|
+
note: "merge asenkron koşuyor; merge_get ile status=applied (ya da applied-with-drift) bekleyin"
|
|
81
|
+
};
|
|
82
|
+
}));
|
|
83
|
+
server.registerTool('merge_refresh', {
|
|
84
|
+
title: 'Merge planını tazele',
|
|
85
|
+
description: "Açık bir planı dalın ve development'ın ŞU ANKİ hâline göre yeniden hesaplatır. İstek yerinde kalır: id'si, başlığı, açıklaması ve geçmişi korunur — yalnız değişiklik listesi yenilenir. KAYDEDİLMİŞ ÇAKIŞMA SEÇİMLERİ DÜŞER: yeni plan yeni elemanlar üretir, çözümler baştan verilir. Yalnız status=planned bir planda çalışır ve dal ready olmalıdır. Bayat bir planı (merge_get → isStale: true, ya da merge_apply 409) düzeltmenin yolu budur; merge_discard + merge_plan istemez. Asenkron: yanıt 202, plan planning durumundadır — merge_get ile planned bekleyin.",
|
|
86
|
+
inputSchema: { projectId: guid, planId: guid },
|
|
87
|
+
annotations: writes
|
|
88
|
+
}, handle(config, async ({ projectId, planId }) => {
|
|
89
|
+
const payload = await platform.org('POST', `/merges/${projectId}/${planId}/refresh`);
|
|
90
|
+
return {
|
|
91
|
+
...(payload && typeof payload === 'object' ? payload : { data: payload }),
|
|
92
|
+
note: "plan yeniden hesaplanıyor; merge_get ile status=planned bekleyin — çakışma çözümleri baştan verilir"
|
|
93
|
+
};
|
|
94
|
+
}));
|
|
95
|
+
server.registerTool('merge_revert', {
|
|
96
|
+
title: "Uygulanmış merge'i geri al",
|
|
97
|
+
description: "Uygulanmış bir merge'i geri alan YENİ bir plan açar; kaynak plan silinmez, reverted olur ve geçmişte kalır. Yalnız status=applied|applied-with-drift ve kind=merge planlar geri alınabilir, pencere RevertWindowDays (varsayılan 7 gün) — dışında 400 döner. Ters plan kaynak plandaki her işlemi tersine çevirir: eklenen kayıt silinir, silinen geri gelir, değişen eski gövdesine döner. Merge'den SONRA elle değiştirilen kayıtlar conflict gelir ve orada source = merge öncesi değer, target = şimdiki değer demektir. Asenkron: yanıt 202, ters plan planning durumundadır.",
|
|
98
|
+
inputSchema: { projectId: guid, planId: guid },
|
|
99
|
+
annotations: writes
|
|
100
|
+
}, handle(config, async ({ projectId, planId }) => {
|
|
101
|
+
const payload = await platform.org('POST', `/merges/${projectId}/${planId}/revert`, { body: { projectId, planId } });
|
|
102
|
+
return {
|
|
103
|
+
...(payload && typeof payload === 'object' ? payload : { data: payload }),
|
|
104
|
+
note: "ters plan hesaplanıyor; merge_get ile status=planned bekleyin, sonra merge_apply"
|
|
105
|
+
};
|
|
106
|
+
}));
|
|
107
|
+
server.registerTool('merge_discard', {
|
|
108
|
+
title: 'Merge planından vazgeç',
|
|
109
|
+
description: "Planı uygulamadan kapatır. Uygulanmakta olan (status=applying) bir plan bırakılamaz — 400 döner. Dal ve şeması olduğu gibi kalır.",
|
|
110
|
+
inputSchema: { projectId: guid, planId: guid },
|
|
111
|
+
annotations: destructive
|
|
112
|
+
}, handle(config, ({ projectId, planId }) => platform.org('DELETE', `/merges/${projectId}/${planId}`)));
|
|
113
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { guid, projectParam, environmentParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
|
|
2
|
+
import { constraintSummary } from '../knowledge/backendConstraints.js';
|
|
3
|
+
import { unmappedMembers, withUnmappedMembers } from '../knowledge/schemaGuard.js';
|
|
4
|
+
import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
|
|
5
|
+
import { policyRules } from '../knowledge/rules.js';
|
|
6
|
+
import { handle } from '../platform/responses.js';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
const policyOverrideParam = z.boolean().optional()
|
|
9
|
+
.describe("true: politika block bulgularını warn'a indirir (bilinçli istisna; yanıtta işaretlenir)");
|
|
10
|
+
const behaviorParam = z.enum(['primitive', 'object', 'array']);
|
|
11
|
+
const statusParam = z.enum(['draft', 'publishing', 'published', 'publish-failed']);
|
|
12
|
+
const environmentVariablesParam = z.record(z.string())
|
|
13
|
+
.describe("Runtime'a geçen env değişkenleri; anahtar ^[A-Z_][A-Z0-9_]*$ ve toplam ≤ 4 KB (Lambda sınırı). Fonksiyon bunları Node'da process.env, Python'da os.environ üzerinden okur — SIR DEĞER KOYMAYIN: kayıt düz metin tutulur");
|
|
14
|
+
// Create ve Update aynı gövdeyi taşır (Update yalnız id ekler): iki tool'un şeması tek yerde durur,
|
|
15
|
+
// backend komutlarındaki alan çifti ayrışırsa `unmappedMembers` iki tool'da da aynı anda konuşur.
|
|
16
|
+
const definitionInput = {
|
|
17
|
+
name: z.string().min(1).max(255).describe("Fonksiyon adı; tenant içinde BENZERSİZ. Slug bundan türetilir ve runtime fonksiyon adına girer (zf-{proje}-{ortam}-{slug}). SLUG DA BENZERSİZDİR: 'Sipariş' ile 'Siparis' farklı adlardır ama aynı slug'ı üretir ve ikincisi 409 alır"),
|
|
18
|
+
description: z.string().optional().describe('Fonksiyonun ne yaptığı; Studio listesinde görünür, çalışma zamanını etkilemez'),
|
|
19
|
+
language: z.enum(['node', 'python']).describe("Çalışma dili. Inline kaynakta dosya adı sabittir: node → function.js (exports.handler = async (args, context) => …), python → function.py (def handler(args, context))"),
|
|
20
|
+
provider: z.enum(['built-in', 'aws-lambda']).describe("'built-in' platformun kendi Fission kümesinde koşar ve runtime hesabı İSTEMEZ (runtimeId boş kalır); 'aws-lambda' müşterinin kendi hesabında koşar ve runtimeId ZORUNLUDUR. 'azure-functions' enum'da tanımlı ama v1'de reddedilir (Faz 2)"),
|
|
21
|
+
runtimeId: guid.optional().describe("AWS Lambda hesabı (microfunction_runtime_list). provider 'built-in' ise VERİLMEZ; 'aws-lambda' ise zorunlu ve hesabın provider'ı fonksiyonunkiyle aynı olmalı"),
|
|
22
|
+
sourceKind: z.enum(['inline', 'archive']).describe("'inline' ⇒ code zorunlu (≤ 512 KB, standart kütüphane; bağımlılık YOK); 'archive' ⇒ önce kayıt açılır, zip Studio'dan yüklenir (MCP'de zip yükleme yok — Faz 2) ve entrypoint 'modül.fonksiyon' olarak verilir"),
|
|
23
|
+
code: z.string().optional().describe("Inline kaynak kodu. Sözleşme: tek bir handler dışa aktarılır, args çağrı argümanları, context SALT OKUNUR anlık görüntüdür (input/variables/steps/user/request/language/project/environment). Yazma yalnız dönüş değeriyle olur. Ayrıntı: platform_guide('microfunction')"),
|
|
24
|
+
entrypoint: z.string().optional().describe("Zip kaynağında 'modül.fonksiyon' (ör. 'handlers/sum.handler'). Inline kaynakta gateway'in de platformun da beklediği sabit 'function.handler'dır — vermeyin"),
|
|
25
|
+
parameterDtoId: guid.optional().describe("Çağrı argümanlarının sözleşmesi (DataTransferObject). Workflow step'i ifadeyi bu şemaya göre yazar; boşsa args = {} gider"),
|
|
26
|
+
parameterBehavior: behaviorParam.optional().describe('parameterDtoId verildiyse zorunlu: primitive | object | array'),
|
|
27
|
+
returnDtoId: guid.optional().describe('Dönüş sözleşmesi; step çıktısının şeması bundan üretilir'),
|
|
28
|
+
returnBehavior: behaviorParam.optional().describe('returnDtoId verildiyse zorunlu: primitive | object | array'),
|
|
29
|
+
timeoutSeconds: z.number().int().min(1).max(900).optional().describe("Fonksiyon bütçesi; varsayılan 30. Built-In üst sınırı 300, Lambda 900. Bütçe dolarsa çağrı MicrofunctionInvocationTimeoutException ile step'in fail dalına düşer"),
|
|
30
|
+
memoryMb: z.number().int().min(128).max(3008).optional().describe("Varsayılan 256. Built-In'de yalnız kaydedilir (küme environment limiti geçerlidir); Lambda'da gerçekten uygulanır"),
|
|
31
|
+
environmentVariables: environmentVariablesParam.optional(),
|
|
32
|
+
publish: z.boolean().optional().describe("true: kayıt açıldıktan/güncellendikten sonra aynı çağrıda yayın istenir ('Kaydet ve yayınla'). Yayın ASENKRONDUR — çağrı status: 'publishing' döner; sonucu microfunction_get ile izleyin (published | publish-failed + publishError)")
|
|
33
|
+
};
|
|
34
|
+
const runtimeInput = {
|
|
35
|
+
name: z.string().min(1).max(255).describe('Hesap adı; benzersiz'),
|
|
36
|
+
provider: z.literal('aws-lambda').optional().describe("v1'de yalnız 'aws-lambda' kabul edilir; gönderilmezse gateway onu koyar"),
|
|
37
|
+
region: z.string().min(1).describe("AWS bölge kodu, ör. 'eu-central-1'"),
|
|
38
|
+
accessKeyId: z.string().min(1).describe('AWS access key id'),
|
|
39
|
+
secretAccessKey: z.string().min(1).describe("AWS secret access key. Kayıt düz metin tutulur; platform DTO'da maskeler (**** + son 4), gateway bu tool'ların yanıtında değeri tümüyle '***' yapar"),
|
|
40
|
+
roleArn: z.string().optional().describe("Lambda execution rolü: arn:aws:iam::<12 hane>:role/<ad>. Rolü müşteri oluşturur; fonksiyon bu rolle koşar")
|
|
41
|
+
};
|
|
42
|
+
export function registerMicrofunctionTools(server, platform) {
|
|
43
|
+
const config = platform.config;
|
|
44
|
+
server.registerTool('microfunction_list', {
|
|
45
|
+
title: 'Microfunction listele',
|
|
46
|
+
description: "Proje kapsamındaki microfunction'ları listeler. Workflow step'i kurmadan ÖNCE bunu status: 'published' ile çağırın: step yalnız yayındaki kaydı koşturur, taslak seçilirse koşum fail dalına düşer (WF-MF-001). Süzgeç ve sayfalama SUNUCUDA uygulanır: searchTerm ad/slug/açıklama içinde arar, sayfa boyu verilmezse gateway 200 gönderir (platform varsayılanı 10'dur).",
|
|
47
|
+
inputSchema: {
|
|
48
|
+
project: projectParam,
|
|
49
|
+
environment: environmentParam,
|
|
50
|
+
status: statusParam.optional().describe("Durum süzgeci; step editörü için 'published'"),
|
|
51
|
+
searchTerm: z.string().optional().describe('Ad, slug ve açıklamada geçen metin; büyük/küçük harf duyarsız'),
|
|
52
|
+
...pagingParams
|
|
53
|
+
},
|
|
54
|
+
annotations: readOnly
|
|
55
|
+
}, handle(config, ({ project, environment, status, searchTerm, pageIndex, pageLength, order, isDescending }) => platform.tenant('GET', project, '/microfunctions', {
|
|
56
|
+
environment,
|
|
57
|
+
query: { status, searchTerm, pageIndex: pageIndex ?? 0, pageLength: pageLength ?? 200, order, isDescending }
|
|
58
|
+
})));
|
|
59
|
+
server.registerTool('microfunction_get', {
|
|
60
|
+
title: 'Microfunction getir',
|
|
61
|
+
description: "Tek bir microfunction'ın tanımını ve yayın durumunu getirir: status, revision/publishedRevision, hasUnpublishedChanges (kayıt yayında ama üstüne yazılmış), publishError, parameterScheme/returnScheme. Yayın asenkron olduğu için 'publishing' durumunda birkaç saniye arayla tekrar çağırın.",
|
|
62
|
+
inputSchema: { project: projectParam, environment: environmentParam, id: guid },
|
|
63
|
+
annotations: readOnly
|
|
64
|
+
}, handle(config, ({ project, environment, id }) => platform.tenant('GET', project, `/microfunctions/${id}`, { environment })));
|
|
65
|
+
server.registerTool('microfunction_create', {
|
|
66
|
+
title: 'Microfunction tanımla',
|
|
67
|
+
description: "Yeni microfunction kaydeder. Kayıt DRAFT doğar; yayına almadan hiçbir workflow step'i onu koşturamaz (publish: true ile aynı çağrıda yayın isteyebilirsiniz). Yayın adımı kodu runtime'a kurar ve bir 'probe' çağrısıyla duman testi yapar: sözdizimi/import hatası yayın anında publish-failed olur, koşumda değil. Protokol ve sınırlar: platform_guide('microfunction'). " + constraintSummary('CreateMicrofunctionCommand'),
|
|
68
|
+
inputSchema: { project: projectParam, environment: environmentParam, ...definitionInput, policyOverride: policyOverrideParam },
|
|
69
|
+
annotations: writes
|
|
70
|
+
}, handle(config, async ({ project, environment, policyOverride, ...body }) => {
|
|
71
|
+
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'microfunction', body, { operation: 'create' }, policyOverride));
|
|
72
|
+
const drift = unmappedMembers('CreateMicrofunctionCommand', body);
|
|
73
|
+
return attachFindings(withUnmappedMembers(await platform.tenant('POST', project, '/microfunctions', { environment, body }), drift), findings);
|
|
74
|
+
}));
|
|
75
|
+
server.registerTool('microfunction_update', {
|
|
76
|
+
title: 'Microfunction güncelle',
|
|
77
|
+
description: "Microfunction'ın TAMAMINI değiştirir (PUT — değiştirmediğiniz alanları da gönderin; önce microfunction_get). Her güncelleme revision'ı artırır: yayındaki kayıt güncellenirse runtime ESKİ sürümü koşmaya devam eder ve kayıt hasUnpublishedChanges: true olur — yeni kodu canlıya almak için publish: true verin ya da microfunction_publish çağırın. PROVIDER YAYINDAYKEN DEĞİŞMEZ: bir kez yayınlanmış kaydın provider'ını değiştiren PUT 400 döner (MicrofunctionProviderChangeNotAllowedException) — kaydı silip yeniden oluşturun. " + constraintSummary('UpdateMicrofunctionCommand'),
|
|
78
|
+
inputSchema: { project: projectParam, environment: environmentParam, id: guid, ...definitionInput, policyOverride: policyOverrideParam },
|
|
79
|
+
annotations: updates
|
|
80
|
+
}, handle(config, async ({ project, environment, id, policyOverride, ...body }) => {
|
|
81
|
+
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'microfunction', body, { operation: 'update' }, policyOverride));
|
|
82
|
+
const drift = unmappedMembers('UpdateMicrofunctionCommand', body);
|
|
83
|
+
return attachFindings(withUnmappedMembers(await platform.tenant('PUT', project, `/microfunctions/${id}`, { environment, body }), drift), findings);
|
|
84
|
+
}));
|
|
85
|
+
server.registerTool('microfunction_publish', {
|
|
86
|
+
title: 'Microfunction yayınla',
|
|
87
|
+
description: "Kaydı runtime'a kurar. Çağrı 202 ile hemen döner ve kayıt 'publishing' olur; sonucu microfunction_get ile izleyin — 'published' (publishedReference: Fission fonksiyon adı ya da Lambda ARN) ya da 'publish-failed' (publishError). Zaten yayınlanmakta olan bir kayıt için çağrı 400 döner.",
|
|
88
|
+
inputSchema: { project: projectParam, environment: environmentParam, id: guid },
|
|
89
|
+
annotations: updates
|
|
90
|
+
}, handle(config, ({ project, environment, id }) => platform.tenant('POST', project, `/microfunctions/${id}/publish`, { environment })));
|
|
91
|
+
server.registerTool('microfunction_delete', {
|
|
92
|
+
title: 'Microfunction sil',
|
|
93
|
+
description: "Microfunction'ı siler ve runtime'daki fonksiyonu kaldırır. Bir workflow step'i kaydı kullanıyorsa 400 ile reddedilir (CantDeleteUsedMicrofunctionException) ve mesaj çağıran akışın adını taşır — önce step'i kaldırın.",
|
|
94
|
+
inputSchema: { project: projectParam, environment: environmentParam, id: guid },
|
|
95
|
+
annotations: destructive
|
|
96
|
+
}, handle(config, ({ project, environment, id }) => platform.tenant('DELETE', project, `/microfunctions/${id}`, { environment })));
|
|
97
|
+
server.registerTool('microfunction_invoke', {
|
|
98
|
+
title: 'Microfunction çağır',
|
|
99
|
+
description: "Yayındaki fonksiyonu GERÇEK runtime'da koşturur ve zarfı döner: { ok, output, error, logs, durationMs }. Test çağrısıdır ama rollback YOKTUR — fonksiyon dış dünyaya ne yaptıysa kalır. ok:false ise error.type/message/stack okunur; aynı hata workflow'da step'i fail dalına düşürür. Taslak kayıtta çağrı MicrofunctionNotPublishedException ile döner. Kaydetmeden koşma yolu yoktur: önce microfunction_update + publish.",
|
|
100
|
+
inputSchema: {
|
|
101
|
+
project: projectParam,
|
|
102
|
+
environment: environmentParam,
|
|
103
|
+
id: guid,
|
|
104
|
+
args: z.record(z.unknown()).optional().describe("Fonksiyona giden argüman nesnesi (handler'ın ilk parametresi); parameterDtoId varsa o şemaya uymalı"),
|
|
105
|
+
preview: z.boolean().optional().describe("true: shim logları (console.log/print) zarfın logs alanında döner. false/boş: logs boş gelir — hata ayıklarken açın")
|
|
106
|
+
},
|
|
107
|
+
annotations: updates
|
|
108
|
+
}, handle(config, ({ project, environment, id, args, preview }) => platform.tenant('POST', project, `/microfunctions/${id}/invoke`, { environment, body: { args, preview } })));
|
|
109
|
+
server.registerTool('microfunction_runtime_list', {
|
|
110
|
+
title: 'Microfunction runtime hesaplarını listele',
|
|
111
|
+
description: "Bulut runtime hesaplarını (v1: AWS Lambda) listeler; secretAccessKey maskeli döner. Built-In fonksiyonların runtime kaydı YOKTUR — liste boşsa yalnız provider: 'built-in' fonksiyon tanımlayabilirsiniz.",
|
|
112
|
+
inputSchema: { project: projectParam, environment: environmentParam },
|
|
113
|
+
annotations: readOnly
|
|
114
|
+
}, handle(config, ({ project, environment }) => platform.tenant('GET', project, '/microfunction-runtimes', { environment })));
|
|
115
|
+
server.registerTool('microfunction_runtime_get', {
|
|
116
|
+
title: 'Microfunction runtime hesabını getir',
|
|
117
|
+
description: 'Tek bir runtime hesabını getirir (secretAccessKey maskeli).',
|
|
118
|
+
inputSchema: { project: projectParam, environment: environmentParam, id: guid },
|
|
119
|
+
annotations: readOnly
|
|
120
|
+
}, handle(config, ({ project, environment, id }) => platform.tenant('GET', project, `/microfunction-runtimes/${id}`, { environment })));
|
|
121
|
+
server.registerTool('microfunction_runtime_create', {
|
|
122
|
+
title: 'Microfunction runtime hesabı tanımla',
|
|
123
|
+
description: "AWS Lambda hesabı kaydeder: bölge + erişim anahtarları + Lambda execution rolü (roleArn). Kimlik bilgileri düz metin tutulur (Datasource emsali) — dar yetkili bir IAM kullanıcısı verin. Kaydettikten sonra microfunction_runtime_test_connection ile doğrulayın. " + constraintSummary('CreateMicrofunctionRuntimeCommand'),
|
|
124
|
+
inputSchema: { project: projectParam, environment: environmentParam, ...runtimeInput, policyOverride: policyOverrideParam },
|
|
125
|
+
annotations: writes
|
|
126
|
+
}, handle(config, async ({ project, environment, policyOverride, ...input }) => {
|
|
127
|
+
const body = { ...input, provider: input.provider ?? 'aws-lambda' };
|
|
128
|
+
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'microfunctionRuntime', body, { operation: 'create' }, policyOverride));
|
|
129
|
+
const drift = unmappedMembers('CreateMicrofunctionRuntimeCommand', body);
|
|
130
|
+
return attachFindings(withUnmappedMembers(await platform.tenant('POST', project, '/microfunction-runtimes', { environment, body }), drift), findings);
|
|
131
|
+
}));
|
|
132
|
+
server.registerTool('microfunction_runtime_update', {
|
|
133
|
+
title: 'Microfunction runtime hesabını güncelle',
|
|
134
|
+
description: 'Runtime hesabının tamamını değiştirir (PUT). secretAccessKey maskeli okunduğu için güncellemede GERÇEK değeri yeniden gönderin — maskeyi geri yazmayın. ' + constraintSummary('UpdateMicrofunctionRuntimeCommand'),
|
|
135
|
+
inputSchema: { project: projectParam, environment: environmentParam, id: guid, ...runtimeInput, policyOverride: policyOverrideParam },
|
|
136
|
+
annotations: updates
|
|
137
|
+
}, handle(config, async ({ project, environment, id, policyOverride, ...input }) => {
|
|
138
|
+
const body = { ...input, provider: input.provider ?? 'aws-lambda' };
|
|
139
|
+
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'microfunctionRuntime', body, { operation: 'update' }, policyOverride));
|
|
140
|
+
const drift = unmappedMembers('UpdateMicrofunctionRuntimeCommand', body);
|
|
141
|
+
return attachFindings(withUnmappedMembers(await platform.tenant('PUT', project, `/microfunction-runtimes/${id}`, { environment, body }), drift), findings);
|
|
142
|
+
}));
|
|
143
|
+
server.registerTool('microfunction_runtime_delete', {
|
|
144
|
+
title: 'Microfunction runtime hesabını sil',
|
|
145
|
+
description: 'Runtime hesabını siler. Bir microfunction hesabı kullanıyorsa 400 ile reddedilir (CantDeleteUsedMicrofunctionRuntimeException).',
|
|
146
|
+
inputSchema: { project: projectParam, environment: environmentParam, id: guid },
|
|
147
|
+
annotations: destructive
|
|
148
|
+
}, handle(config, ({ project, environment, id }) => platform.tenant('DELETE', project, `/microfunction-runtimes/${id}`, { environment })));
|
|
149
|
+
server.registerTool('microfunction_runtime_test_connection', {
|
|
150
|
+
title: 'Microfunction runtime bağlantısını test et',
|
|
151
|
+
description: "Kayıtlı hesabın kimlik bilgilerini gerçek bulut ucunda dener (Lambda: ListFunctions). Hata 400 olarak döner ve mesaj AWS'in kendi cümlesidir — yayın denemeden önce buradan doğrulayın.",
|
|
152
|
+
inputSchema: { project: projectParam, environment: environmentParam, id: guid },
|
|
153
|
+
annotations: updates
|
|
154
|
+
}, handle(config, ({ project, environment, id }) => platform.tenant('POST', project, `/microfunction-runtimes/${id}/test-connection`, { environment })));
|
|
155
|
+
}
|
|
@@ -80,7 +80,8 @@ const executeInput = {
|
|
|
80
80
|
anonymous: z.boolean().optional().describe("true: istek HİÇBİR Authorization header'ı olmadan gider — 'korumalı uç tokensız ne döner?' sorusunun cevabı budur (korumalı uçtan 401, anonim uçtan 200 beklenir). Gateway normalde her çağrıya kendi token'ını koyduğu için bu bayrak olmadan anonim erişim testi yapılamaz."),
|
|
81
81
|
asUser: asUserParam,
|
|
82
82
|
cookies: cookiesParam,
|
|
83
|
-
language: languageParam
|
|
83
|
+
language: languageParam,
|
|
84
|
+
expectFailure: z.boolean().optional().describe("true: bu çağrıda bir hata dönmesi BEKLENİYOR (kasıtlı negatif test — yanlış parola, yetkisiz erişim, geçersiz gövde). Yanıt yine httpStatus'u/hatayı taşır, ama ölçüm aracı bu satırı plansız hata saymaz (page_screenshot'taki expectFailure deseniyle aynı).")
|
|
84
85
|
};
|
|
85
86
|
/**
|
|
86
87
|
* `anonymous` ile kimlik birlikte verilemez: biri kimliksiz isteği, diğeri belirli bir üyenin
|
|
@@ -382,8 +383,46 @@ export function registerMicroserviceTools(server, platform) {
|
|
|
382
383
|
}));
|
|
383
384
|
server.registerTool('microservice_job_history', {
|
|
384
385
|
title: 'Job geçmişi',
|
|
385
|
-
description: 'Zamanlanmış (job tipli) microservice\'in çalışma geçmişini listeler.',
|
|
386
|
-
inputSchema: {
|
|
386
|
+
description: 'Zamanlanmış (job tipli) microservice\'in çalışma geçmişini listeler. status ile yalnız running | completed | failed koşular süzülür; order TriggeredAt | CompletedAt | ExecutionStatus | DurationMs.',
|
|
387
|
+
inputSchema: {
|
|
388
|
+
project: projectParam,
|
|
389
|
+
environment: environmentParam,
|
|
390
|
+
id: guid,
|
|
391
|
+
status: z.enum(['running', 'completed', 'failed']).optional().describe('Yalnız bu durumdaki koşular'),
|
|
392
|
+
...pagingParams
|
|
393
|
+
},
|
|
387
394
|
annotations: readOnly
|
|
388
395
|
}, handle(config, ({ project, environment, id, ...query }) => platform.tenant('GET', project, `/microservices/${id}/job-history`, { environment, query })));
|
|
396
|
+
server.registerTool('microservice_schedule_get', {
|
|
397
|
+
title: 'Zamanlama durumu',
|
|
398
|
+
description: 'Zamanlanmış (schedule-job) microservice\'in canlı zamanlama durumunu döner: state (scheduled | paused | notScheduled | error | ended), isRunning, nextFireTimeUtc, previousFireTimeUtc, sıradaki 5 koşu (upcomingFireTimesUtc), cron + timeZoneId, serverTimeUtc ve son koşunun özeti (lastExecution). "Bir sonraki sefer ne zaman koşacak?" sorusunu cron\'u yorumlamadan buradan cevaplayın. paused iken nextFireTimeUtc null ve upcoming boştur. notScheduled: kayıt var ama zamanlayıcıda trigger yok — en geç 5 dk içinde uzlaştırma kurar ya da servisi kaydedin (microservice_update). ended: tanımın sonraki koşusu yok (once geçti, end on/after doldu). schedule alanı tanımın kendisidir. Schedule tipli olmayan servis 400 döner.',
|
|
399
|
+
inputSchema: { project: projectParam, environment: environmentParam, id: guid },
|
|
400
|
+
annotations: readOnly
|
|
401
|
+
}, handle(config, ({ project, environment, id }) => platform.tenant('GET', project, `/microservices/${id}/schedule`, { environment })));
|
|
402
|
+
server.registerTool('microservice_schedule_forecast', {
|
|
403
|
+
title: 'Zamanlama öngörüsü',
|
|
404
|
+
description: 'Bir zamanlama tanımını (typeParameters.schedule biçimi) KAYDETMEDEN doğrular ve sıradaki 5 koşuyu UTC olarak döner: { isValid, errors[{property,message}], upcomingFireTimesUtc[] }. microservice_create/update öncesi tanımın beklenen saatlerde koştuğunu buradan görün; alan listesi için platform_guide(\'microservice\') §Zamanlanmış iş.',
|
|
405
|
+
inputSchema: {
|
|
406
|
+
project: projectParam,
|
|
407
|
+
environment: environmentParam,
|
|
408
|
+
schedule: z.record(z.unknown()).describe('ScheduleDefinition: mode, timeZoneId, startAt, every, unit, timeOfDay, daysOfWeek, daysOfMonth, cronExpression, end')
|
|
409
|
+
},
|
|
410
|
+
annotations: readOnly
|
|
411
|
+
}, handle(config, ({ project, environment, schedule }) => platform.tenant('POST', project, '/microservices/schedule/preview', { environment, body: { schedule } })));
|
|
412
|
+
server.registerTool('microservice_schedule_control', {
|
|
413
|
+
title: 'Zamanlamayı yönet',
|
|
414
|
+
description: 'Zamanlanmış microservice\'i duraklatır (pause), sürdürür (resume) ya da cron\'u beklemeden bir kez koşturur (trigger). pause/resume yalnız zamanlayıcıdaki durumu değiştirir, tanımı değil; duraklatma cron güncellemesi ve deployment senkronundan sonra da korunur. resume: duraklatılmışken bir koşu kaçırıldıysa iş bir kez hemen koşar. trigger KUYRUĞA ALIR, sonucu beklemez: deployment ya da template kurulumu sürüyorsa koşu veto edilir ve geçmişe hiç düşmez — sonucu microservice_job_history ile doğrulayın. Zamanlayıcıda kaydı olmayan iş 400 (ScheduleJobNotRegisteredException) döner. Production ortamında üç eylem de 400 CantWriteInProductionException ile reddedilir; trigger ayrıca deployment/template kurulumu sürerken reddedilir.',
|
|
415
|
+
inputSchema: {
|
|
416
|
+
project: projectParam,
|
|
417
|
+
environment: environmentParam,
|
|
418
|
+
id: guid,
|
|
419
|
+
action: z.enum(['pause', 'resume', 'trigger']).describe('pause | resume | trigger')
|
|
420
|
+
},
|
|
421
|
+
annotations: writes
|
|
422
|
+
}, handle(config, async ({ project, environment, id, action }) => {
|
|
423
|
+
await platform.tenant('POST', project, `/microservices/${id}/schedule/${action}`, { environment });
|
|
424
|
+
return action === 'trigger'
|
|
425
|
+
? { action, accepted: true, note: 'Kuyruğa alındı; koşuyu microservice_job_history ile doğrulayın.' }
|
|
426
|
+
: { action, accepted: true, note: 'Güncel durum için microservice_schedule_get.' };
|
|
427
|
+
}));
|
|
389
428
|
}
|