@edugate/ai-engine 0.7.0
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/README.md +29 -0
- package/dist/index.d.mts +383 -0
- package/dist/index.d.ts +383 -0
- package/dist/index.js +13 -0
- package/dist/index.mjs +13 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# `@edugate/ai-engine`
|
|
2
|
+
|
|
3
|
+
Come Edugate governa un modello.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
llm/ l'adattatore, e il BUDGET di una chiamata: quante richieste, quanto
|
|
7
|
+
tempo, cosa fare quando fallisce
|
|
8
|
+
session/ ciò che si porta da un turno all'altro, quando Dify ne concatena più
|
|
9
|
+
d'uno nello stesso giro
|
|
10
|
+
planning/ il controllo di FEDELTÀ — uno specialista a volte DICE in prosa di
|
|
11
|
+
aver fatto qualcosa che nelle azioni emesse non c'è
|
|
12
|
+
mcp/ registrare uno specialista come tool, più sessione e media
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
⚠️ **Non si chiama `agent-*` di proposito.** Di duemilacinquecento righe, solo
|
|
16
|
+
ottocentosettanta parlano di agenti: le altre non sanno nemmeno cosa sia un
|
|
17
|
+
agente. E non `mcp-*`: adattatore, budget, sessione e fedeltà funzionerebbero su
|
|
18
|
+
qualunque trasporto, e `apps/api` — il server che l'MCP di Edugate lo espone
|
|
19
|
+
davvero — questo pacchetto non lo usa.
|
|
20
|
+
|
|
21
|
+
«engine» sta a `builder-engine` come questo sta a Dify: **un motore esegue, e
|
|
22
|
+
qualcun altro decide**. Chi decide quale specialista chiamare è l'orchestratore,
|
|
23
|
+
che oggi è Dify e vive fuori.
|
|
24
|
+
|
|
25
|
+
**Le definizioni** dei sette specialisti sono in `@edugate/authoring/agents`,
|
|
26
|
+
perché sono conoscenza di authoring — e le espone anche l'MCP dei clienti, come
|
|
27
|
+
`prompts` MCP.
|
|
28
|
+
|
|
29
|
+
Documenti in [`docs/`](./docs/README.md).
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AgentDefinition } from '@edugate/authoring/agents';
|
|
3
|
+
export * from '@edugate/authoring/agents';
|
|
4
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
5
|
+
|
|
6
|
+
declare const AgentResponseSchema: z.ZodObject<{
|
|
7
|
+
text: z.ZodString;
|
|
8
|
+
actionMeta: z.ZodOptional<z.ZodObject<{
|
|
9
|
+
type: z.ZodString;
|
|
10
|
+
targetId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
11
|
+
}, "strip", z.ZodTypeAny, {
|
|
12
|
+
type: string;
|
|
13
|
+
targetId?: string | null | undefined;
|
|
14
|
+
}, {
|
|
15
|
+
type: string;
|
|
16
|
+
targetId?: string | null | undefined;
|
|
17
|
+
}>>;
|
|
18
|
+
choices: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
19
|
+
question: z.ZodString;
|
|
20
|
+
elementId: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
21
|
+
options: z.ZodArray<z.ZodString, "many">;
|
|
22
|
+
}, "strip", z.ZodTypeAny, {
|
|
23
|
+
options: string[];
|
|
24
|
+
question: string;
|
|
25
|
+
elementId?: string | null | undefined;
|
|
26
|
+
}, {
|
|
27
|
+
options: string[];
|
|
28
|
+
question: string;
|
|
29
|
+
elementId?: string | null | undefined;
|
|
30
|
+
}>, "many">>;
|
|
31
|
+
revertSnapshot: z.ZodOptional<z.ZodAny>;
|
|
32
|
+
delegateTo: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
33
|
+
}, "strip", z.ZodTypeAny, {
|
|
34
|
+
text: string;
|
|
35
|
+
actionMeta?: {
|
|
36
|
+
type: string;
|
|
37
|
+
targetId?: string | null | undefined;
|
|
38
|
+
} | undefined;
|
|
39
|
+
choices?: {
|
|
40
|
+
options: string[];
|
|
41
|
+
question: string;
|
|
42
|
+
elementId?: string | null | undefined;
|
|
43
|
+
}[] | undefined;
|
|
44
|
+
revertSnapshot?: any;
|
|
45
|
+
delegateTo?: string | null | undefined;
|
|
46
|
+
}, {
|
|
47
|
+
text: string;
|
|
48
|
+
actionMeta?: {
|
|
49
|
+
type: string;
|
|
50
|
+
targetId?: string | null | undefined;
|
|
51
|
+
} | undefined;
|
|
52
|
+
choices?: {
|
|
53
|
+
options: string[];
|
|
54
|
+
question: string;
|
|
55
|
+
elementId?: string | null | undefined;
|
|
56
|
+
}[] | undefined;
|
|
57
|
+
revertSnapshot?: any;
|
|
58
|
+
delegateTo?: string | null | undefined;
|
|
59
|
+
}>;
|
|
60
|
+
type AgentResponseType = z.infer<typeof AgentResponseSchema>;
|
|
61
|
+
declare const AGENT_RESPONSE_JSON_SCHEMA: {
|
|
62
|
+
readonly type: "object";
|
|
63
|
+
readonly additionalProperties: false;
|
|
64
|
+
readonly required: readonly ["text"];
|
|
65
|
+
readonly properties: {
|
|
66
|
+
readonly text: {
|
|
67
|
+
readonly type: "string";
|
|
68
|
+
};
|
|
69
|
+
readonly actionMeta: {
|
|
70
|
+
readonly type: "object";
|
|
71
|
+
readonly additionalProperties: false;
|
|
72
|
+
readonly required: readonly ["type"];
|
|
73
|
+
readonly properties: {
|
|
74
|
+
readonly type: {
|
|
75
|
+
readonly type: "string";
|
|
76
|
+
};
|
|
77
|
+
readonly targetId: {
|
|
78
|
+
readonly type: readonly ["string", "null"];
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
readonly choices: {
|
|
83
|
+
readonly type: "array";
|
|
84
|
+
readonly items: {
|
|
85
|
+
readonly type: "object";
|
|
86
|
+
readonly additionalProperties: false;
|
|
87
|
+
readonly required: readonly ["question", "options"];
|
|
88
|
+
readonly properties: {
|
|
89
|
+
readonly question: {
|
|
90
|
+
readonly type: "string";
|
|
91
|
+
};
|
|
92
|
+
readonly elementId: {
|
|
93
|
+
readonly type: readonly ["string", "null"];
|
|
94
|
+
};
|
|
95
|
+
readonly options: {
|
|
96
|
+
readonly type: "array";
|
|
97
|
+
readonly items: {
|
|
98
|
+
readonly type: "string";
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
readonly delegateTo: {
|
|
105
|
+
readonly type: readonly ["string", "null"];
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
interface LlmToolDefinition {
|
|
111
|
+
name: string;
|
|
112
|
+
description: string;
|
|
113
|
+
parameters: Record<string, unknown>;
|
|
114
|
+
}
|
|
115
|
+
interface ToolCall {
|
|
116
|
+
id: string;
|
|
117
|
+
type: "function";
|
|
118
|
+
function: {
|
|
119
|
+
name: string;
|
|
120
|
+
arguments: string;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
interface ChatMessage {
|
|
124
|
+
role: "user" | "assistant" | "system" | "tool";
|
|
125
|
+
content: string;
|
|
126
|
+
tool_call_id?: string;
|
|
127
|
+
tool_calls?: ToolCall[];
|
|
128
|
+
}
|
|
129
|
+
interface TokenUsage {
|
|
130
|
+
promptTokens: number;
|
|
131
|
+
completionTokens: number;
|
|
132
|
+
totalTokens: number;
|
|
133
|
+
}
|
|
134
|
+
interface LlmTurnResult {
|
|
135
|
+
response?: AgentResponseType;
|
|
136
|
+
toolCalls?: ToolCall[];
|
|
137
|
+
usage?: TokenUsage;
|
|
138
|
+
}
|
|
139
|
+
interface SamplingOpts {
|
|
140
|
+
temperature?: number;
|
|
141
|
+
seed?: number;
|
|
142
|
+
timeoutMs?: number;
|
|
143
|
+
signal?: AbortSignal;
|
|
144
|
+
}
|
|
145
|
+
interface LlmAdapter {
|
|
146
|
+
executeTurn(systemPrompt: string, history: ChatMessage[], tools?: LlmToolDefinition[], responseSchema?: Record<string, unknown>, opts?: SamplingOpts): Promise<LlmTurnResult>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
interface OpenAICompatibleAdapterConfig {
|
|
150
|
+
baseUrl: string;
|
|
151
|
+
apiKey: string;
|
|
152
|
+
model: string;
|
|
153
|
+
fallbackModels?: string[];
|
|
154
|
+
temperature?: number;
|
|
155
|
+
timeoutMs?: number;
|
|
156
|
+
}
|
|
157
|
+
declare class OpenAICompatibleAdapter implements LlmAdapter {
|
|
158
|
+
private readonly cfg;
|
|
159
|
+
constructor(cfg: OpenAICompatibleAdapterConfig);
|
|
160
|
+
executeTurn(systemPrompt: string, history: ChatMessage[], tools?: LlmToolDefinition[], responseSchema?: Record<string, unknown>, opts?: SamplingOpts): Promise<LlmTurnResult>;
|
|
161
|
+
private post;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
interface BudgetConfig {
|
|
165
|
+
maxToolTurns: number;
|
|
166
|
+
maxLlmCalls: number;
|
|
167
|
+
maxRetriesPerCall: number;
|
|
168
|
+
wallClockMs: number;
|
|
169
|
+
minCallTimeoutMs: number;
|
|
170
|
+
maxCallTimeoutMs: number;
|
|
171
|
+
adaptive: boolean;
|
|
172
|
+
}
|
|
173
|
+
declare const DEFAULT_BUDGET: BudgetConfig;
|
|
174
|
+
type ExhaustReason = "wall_clock" | "llm_calls" | "tool_turns" | null;
|
|
175
|
+
interface BudgetSnapshot {
|
|
176
|
+
elapsedMs: number;
|
|
177
|
+
llmCalls: number;
|
|
178
|
+
retries: number;
|
|
179
|
+
toolTurns: number;
|
|
180
|
+
tokens: {
|
|
181
|
+
prompt: number;
|
|
182
|
+
completion: number;
|
|
183
|
+
total: number;
|
|
184
|
+
};
|
|
185
|
+
errorsByKind: Record<string, number>;
|
|
186
|
+
config: BudgetConfig;
|
|
187
|
+
}
|
|
188
|
+
declare class RunBudget {
|
|
189
|
+
readonly config: BudgetConfig;
|
|
190
|
+
private readonly now;
|
|
191
|
+
readonly startedAt: number;
|
|
192
|
+
llmCalls: number;
|
|
193
|
+
retries: number;
|
|
194
|
+
toolTurns: number;
|
|
195
|
+
promptTokens: number;
|
|
196
|
+
completionTokens: number;
|
|
197
|
+
totalTokens: number;
|
|
198
|
+
private extraToolTurns;
|
|
199
|
+
readonly errorsByKind: Record<string, number>;
|
|
200
|
+
constructor(config: BudgetConfig, now?: () => number);
|
|
201
|
+
elapsedMs(): number;
|
|
202
|
+
remainingMs(): number;
|
|
203
|
+
remainingCalls(): number;
|
|
204
|
+
private toolTurnCeiling;
|
|
205
|
+
canTakeToolTurn(): boolean;
|
|
206
|
+
grantExtraToolTurns(n: number): boolean;
|
|
207
|
+
canStartCall(): {
|
|
208
|
+
ok: boolean;
|
|
209
|
+
reason: ExhaustReason;
|
|
210
|
+
};
|
|
211
|
+
perCallTimeoutMs(): number;
|
|
212
|
+
spendCall(): void;
|
|
213
|
+
spendTokens(u?: {
|
|
214
|
+
promptTokens?: number;
|
|
215
|
+
completionTokens?: number;
|
|
216
|
+
totalTokens?: number;
|
|
217
|
+
}): void;
|
|
218
|
+
spendRetry(): void;
|
|
219
|
+
recordError(kind: string): void;
|
|
220
|
+
snapshot(): BudgetSnapshot;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
interface RetryDeps {
|
|
224
|
+
sleep?: (ms: number) => Promise<void>;
|
|
225
|
+
jitter?: () => number;
|
|
226
|
+
baseDelayMs?: number;
|
|
227
|
+
maxDelayMs?: number;
|
|
228
|
+
}
|
|
229
|
+
declare function callWithRetry<T>(fn: (timeoutMs: number) => Promise<T>, budget: RunBudget, deps?: RetryDeps): Promise<T>;
|
|
230
|
+
|
|
231
|
+
type LlmErrorKind = "rate_limit" | "server" | "timeout" | "network" | "auth" | "bad_request" | "unknown";
|
|
232
|
+
declare class LlmError extends Error {
|
|
233
|
+
readonly kind: LlmErrorKind;
|
|
234
|
+
readonly status?: number | undefined;
|
|
235
|
+
readonly retryAfterMs?: number | undefined;
|
|
236
|
+
readonly cause?: unknown | undefined;
|
|
237
|
+
constructor(kind: LlmErrorKind, message: string, status?: number | undefined, retryAfterMs?: number | undefined, cause?: unknown | undefined);
|
|
238
|
+
get retryable(): boolean;
|
|
239
|
+
}
|
|
240
|
+
declare function classifyStatus(status: number): LlmErrorKind;
|
|
241
|
+
declare function classifyThrown(e: unknown): LlmError;
|
|
242
|
+
declare function parseRetryAfter(headerVal: string | null | undefined, now?: () => number): number | undefined;
|
|
243
|
+
|
|
244
|
+
interface CanvasAction$1 {
|
|
245
|
+
action: string;
|
|
246
|
+
data: unknown;
|
|
247
|
+
message: string;
|
|
248
|
+
}
|
|
249
|
+
type RunStatus = "ok" | "partial" | "failed" | "needs_input";
|
|
250
|
+
interface RunDiagnostics {
|
|
251
|
+
status: RunStatus;
|
|
252
|
+
termination: "final_response" | "partial_actions" | "no_output" | "llm_error" | "budget_exhausted" | "clarification";
|
|
253
|
+
exhausted?: ExhaustReason;
|
|
254
|
+
lastErrorKind?: LlmErrorKind;
|
|
255
|
+
budget: BudgetSnapshot;
|
|
256
|
+
}
|
|
257
|
+
interface AgentRunResult {
|
|
258
|
+
response: AgentResponseType;
|
|
259
|
+
actions: CanvasAction$1[];
|
|
260
|
+
toolTurns: number;
|
|
261
|
+
status: RunStatus;
|
|
262
|
+
diagnostics: RunDiagnostics;
|
|
263
|
+
}
|
|
264
|
+
interface AgentExecutorOptions {
|
|
265
|
+
maxToolTurns?: number;
|
|
266
|
+
budget?: Partial<BudgetConfig>;
|
|
267
|
+
retry?: RetryDeps;
|
|
268
|
+
now?: () => number;
|
|
269
|
+
}
|
|
270
|
+
interface AgentRunInput {
|
|
271
|
+
message: string;
|
|
272
|
+
contextSummary?: string;
|
|
273
|
+
chatHistory?: ChatMessage[];
|
|
274
|
+
}
|
|
275
|
+
declare class AgentExecutor {
|
|
276
|
+
private readonly agent;
|
|
277
|
+
private readonly llm;
|
|
278
|
+
private readonly tools;
|
|
279
|
+
private readonly toolDefs;
|
|
280
|
+
private readonly allowed;
|
|
281
|
+
private readonly budgetCfg;
|
|
282
|
+
private readonly retryDeps;
|
|
283
|
+
private readonly now?;
|
|
284
|
+
constructor(agent: AgentDefinition, llm: LlmAdapter, options?: AgentExecutorOptions);
|
|
285
|
+
run(input: AgentRunInput): Promise<AgentRunResult>;
|
|
286
|
+
private settleFinal;
|
|
287
|
+
private ok;
|
|
288
|
+
private terminate;
|
|
289
|
+
private executeToolCall;
|
|
290
|
+
private finalize;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
interface CreateServerOptions {
|
|
294
|
+
llm: LlmAdapter;
|
|
295
|
+
maxToolTurns?: number;
|
|
296
|
+
budget?: Partial<BudgetConfig>;
|
|
297
|
+
agents?: AgentDefinition[];
|
|
298
|
+
}
|
|
299
|
+
declare function createServer(opts: CreateServerOptions): McpServer;
|
|
300
|
+
|
|
301
|
+
declare function specialistToolName(agent: AgentDefinition): string;
|
|
302
|
+
interface RegisterSpecialistOptions {
|
|
303
|
+
llm: LlmAdapter;
|
|
304
|
+
maxToolTurns?: number;
|
|
305
|
+
budget?: Partial<BudgetConfig>;
|
|
306
|
+
}
|
|
307
|
+
declare function registerSpecialistTool(server: McpServer, agent: AgentDefinition, opts: RegisterSpecialistOptions): void;
|
|
308
|
+
|
|
309
|
+
declare function registerSessionTools(server: McpServer): void;
|
|
310
|
+
|
|
311
|
+
declare function registerMediaTools(server: McpServer): void;
|
|
312
|
+
|
|
313
|
+
interface PlanBlock {
|
|
314
|
+
prefabId?: string;
|
|
315
|
+
blockType?: string;
|
|
316
|
+
role?: string;
|
|
317
|
+
scene?: string;
|
|
318
|
+
label?: string;
|
|
319
|
+
}
|
|
320
|
+
interface PlanScene {
|
|
321
|
+
sceneId?: string;
|
|
322
|
+
title?: string;
|
|
323
|
+
}
|
|
324
|
+
interface StructuredPlan {
|
|
325
|
+
scenes?: PlanScene[];
|
|
326
|
+
blocks: PlanBlock[];
|
|
327
|
+
}
|
|
328
|
+
interface CanvasAction {
|
|
329
|
+
action: string;
|
|
330
|
+
data: any;
|
|
331
|
+
message?: string;
|
|
332
|
+
}
|
|
333
|
+
declare function planBlockKey(b: PlanBlock): string | undefined;
|
|
334
|
+
declare function actionBlockKey(a: CanvasAction): string | undefined;
|
|
335
|
+
declare function parsePlan(raw: unknown): StructuredPlan | undefined;
|
|
336
|
+
declare function derivePlanFromText(text?: unknown): StructuredPlan | undefined;
|
|
337
|
+
interface PlanDiff {
|
|
338
|
+
missingBlocks: PlanBlock[];
|
|
339
|
+
missingScenes: number;
|
|
340
|
+
}
|
|
341
|
+
declare function diffPlan(plan: StructuredPlan, actions: CanvasAction[]): PlanDiff;
|
|
342
|
+
declare function isComplete(diff: PlanDiff): boolean;
|
|
343
|
+
declare function summarizeMissing(diff: PlanDiff): string;
|
|
344
|
+
|
|
345
|
+
interface ClaimRule {
|
|
346
|
+
id: string;
|
|
347
|
+
label: string;
|
|
348
|
+
detect: RegExp[];
|
|
349
|
+
satisfied: (actions: CanvasAction[]) => boolean;
|
|
350
|
+
}
|
|
351
|
+
declare const CLAIM_TO_OP: ClaimRule[];
|
|
352
|
+
interface UnbackedClaim {
|
|
353
|
+
id: string;
|
|
354
|
+
label: string;
|
|
355
|
+
}
|
|
356
|
+
declare function findUnbackedClaims(text: string | undefined, actions: CanvasAction[]): UnbackedClaim[];
|
|
357
|
+
|
|
358
|
+
interface SessionState {
|
|
359
|
+
activity: any;
|
|
360
|
+
activeSceneId?: string;
|
|
361
|
+
selectedBlockId?: string;
|
|
362
|
+
}
|
|
363
|
+
declare function setSessionActivity(sessionId: string, activity: any, activeSceneId?: string, selectedBlockId?: string): SessionState;
|
|
364
|
+
declare function getSession(sessionId: string | undefined): SessionState | undefined;
|
|
365
|
+
declare function clearSession(sessionId: string | undefined): void;
|
|
366
|
+
declare function applySessionActions(sessionId: string | undefined, actions: {
|
|
367
|
+
action: string;
|
|
368
|
+
data: unknown;
|
|
369
|
+
}[]): void;
|
|
370
|
+
declare function applyMutation(activity: any, action: string, data: any): void;
|
|
371
|
+
declare function buildContextString(state: SessionState): string;
|
|
372
|
+
|
|
373
|
+
declare const SERVER_NAME = "edugate-agents";
|
|
374
|
+
declare const SERVER_VERSION = "1.0.0";
|
|
375
|
+
declare const MCP_PROTOCOL_VERSION = "2025-03-26";
|
|
376
|
+
declare const MCP_ENDPOINT_PATH = "/mcp";
|
|
377
|
+
declare const DEFAULT_MAX_TOOL_TURNS = 8;
|
|
378
|
+
declare const DEFAULT_MAX_LLM_CALLS = 24;
|
|
379
|
+
declare const DEFAULT_MAX_RETRIES = 2;
|
|
380
|
+
declare const DEFAULT_WALL_CLOCK_MS = 240000;
|
|
381
|
+
declare const DEFAULT_LLM_TIMEOUT_MS = 120000;
|
|
382
|
+
|
|
383
|
+
export { AGENT_RESPONSE_JSON_SCHEMA, AgentExecutor, type AgentExecutorOptions, AgentResponseSchema, type AgentResponseType, type AgentRunInput, type AgentRunResult, type BudgetConfig, type BudgetSnapshot, CLAIM_TO_OP, type CanvasAction$1 as CanvasAction, type ChatMessage, type ClaimRule, type CreateServerOptions, DEFAULT_BUDGET, DEFAULT_LLM_TIMEOUT_MS, DEFAULT_MAX_LLM_CALLS, DEFAULT_MAX_RETRIES, DEFAULT_MAX_TOOL_TURNS, DEFAULT_WALL_CLOCK_MS, type ExhaustReason, type LlmAdapter, LlmError, type LlmErrorKind, type LlmToolDefinition, type LlmTurnResult, MCP_ENDPOINT_PATH, MCP_PROTOCOL_VERSION, OpenAICompatibleAdapter, type OpenAICompatibleAdapterConfig, type CanvasAction as PlanCanvasAction, type RegisterSpecialistOptions, type RetryDeps, RunBudget, type RunDiagnostics, type RunStatus, SERVER_NAME, SERVER_VERSION, type SamplingOpts, type SessionState, type TokenUsage, type ToolCall, type UnbackedClaim, actionBlockKey, applyMutation, applySessionActions, buildContextString, callWithRetry, classifyStatus, classifyThrown, clearSession, createServer, derivePlanFromText, diffPlan, findUnbackedClaims, getSession, isComplete, parsePlan, parseRetryAfter, planBlockKey, registerMediaTools, registerSessionTools, registerSpecialistTool, setSessionActivity, specialistToolName, summarizeMissing };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AgentDefinition } from '@edugate/authoring/agents';
|
|
3
|
+
export * from '@edugate/authoring/agents';
|
|
4
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
5
|
+
|
|
6
|
+
declare const AgentResponseSchema: z.ZodObject<{
|
|
7
|
+
text: z.ZodString;
|
|
8
|
+
actionMeta: z.ZodOptional<z.ZodObject<{
|
|
9
|
+
type: z.ZodString;
|
|
10
|
+
targetId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
11
|
+
}, "strip", z.ZodTypeAny, {
|
|
12
|
+
type: string;
|
|
13
|
+
targetId?: string | null | undefined;
|
|
14
|
+
}, {
|
|
15
|
+
type: string;
|
|
16
|
+
targetId?: string | null | undefined;
|
|
17
|
+
}>>;
|
|
18
|
+
choices: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
19
|
+
question: z.ZodString;
|
|
20
|
+
elementId: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
21
|
+
options: z.ZodArray<z.ZodString, "many">;
|
|
22
|
+
}, "strip", z.ZodTypeAny, {
|
|
23
|
+
options: string[];
|
|
24
|
+
question: string;
|
|
25
|
+
elementId?: string | null | undefined;
|
|
26
|
+
}, {
|
|
27
|
+
options: string[];
|
|
28
|
+
question: string;
|
|
29
|
+
elementId?: string | null | undefined;
|
|
30
|
+
}>, "many">>;
|
|
31
|
+
revertSnapshot: z.ZodOptional<z.ZodAny>;
|
|
32
|
+
delegateTo: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
33
|
+
}, "strip", z.ZodTypeAny, {
|
|
34
|
+
text: string;
|
|
35
|
+
actionMeta?: {
|
|
36
|
+
type: string;
|
|
37
|
+
targetId?: string | null | undefined;
|
|
38
|
+
} | undefined;
|
|
39
|
+
choices?: {
|
|
40
|
+
options: string[];
|
|
41
|
+
question: string;
|
|
42
|
+
elementId?: string | null | undefined;
|
|
43
|
+
}[] | undefined;
|
|
44
|
+
revertSnapshot?: any;
|
|
45
|
+
delegateTo?: string | null | undefined;
|
|
46
|
+
}, {
|
|
47
|
+
text: string;
|
|
48
|
+
actionMeta?: {
|
|
49
|
+
type: string;
|
|
50
|
+
targetId?: string | null | undefined;
|
|
51
|
+
} | undefined;
|
|
52
|
+
choices?: {
|
|
53
|
+
options: string[];
|
|
54
|
+
question: string;
|
|
55
|
+
elementId?: string | null | undefined;
|
|
56
|
+
}[] | undefined;
|
|
57
|
+
revertSnapshot?: any;
|
|
58
|
+
delegateTo?: string | null | undefined;
|
|
59
|
+
}>;
|
|
60
|
+
type AgentResponseType = z.infer<typeof AgentResponseSchema>;
|
|
61
|
+
declare const AGENT_RESPONSE_JSON_SCHEMA: {
|
|
62
|
+
readonly type: "object";
|
|
63
|
+
readonly additionalProperties: false;
|
|
64
|
+
readonly required: readonly ["text"];
|
|
65
|
+
readonly properties: {
|
|
66
|
+
readonly text: {
|
|
67
|
+
readonly type: "string";
|
|
68
|
+
};
|
|
69
|
+
readonly actionMeta: {
|
|
70
|
+
readonly type: "object";
|
|
71
|
+
readonly additionalProperties: false;
|
|
72
|
+
readonly required: readonly ["type"];
|
|
73
|
+
readonly properties: {
|
|
74
|
+
readonly type: {
|
|
75
|
+
readonly type: "string";
|
|
76
|
+
};
|
|
77
|
+
readonly targetId: {
|
|
78
|
+
readonly type: readonly ["string", "null"];
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
readonly choices: {
|
|
83
|
+
readonly type: "array";
|
|
84
|
+
readonly items: {
|
|
85
|
+
readonly type: "object";
|
|
86
|
+
readonly additionalProperties: false;
|
|
87
|
+
readonly required: readonly ["question", "options"];
|
|
88
|
+
readonly properties: {
|
|
89
|
+
readonly question: {
|
|
90
|
+
readonly type: "string";
|
|
91
|
+
};
|
|
92
|
+
readonly elementId: {
|
|
93
|
+
readonly type: readonly ["string", "null"];
|
|
94
|
+
};
|
|
95
|
+
readonly options: {
|
|
96
|
+
readonly type: "array";
|
|
97
|
+
readonly items: {
|
|
98
|
+
readonly type: "string";
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
readonly delegateTo: {
|
|
105
|
+
readonly type: readonly ["string", "null"];
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
interface LlmToolDefinition {
|
|
111
|
+
name: string;
|
|
112
|
+
description: string;
|
|
113
|
+
parameters: Record<string, unknown>;
|
|
114
|
+
}
|
|
115
|
+
interface ToolCall {
|
|
116
|
+
id: string;
|
|
117
|
+
type: "function";
|
|
118
|
+
function: {
|
|
119
|
+
name: string;
|
|
120
|
+
arguments: string;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
interface ChatMessage {
|
|
124
|
+
role: "user" | "assistant" | "system" | "tool";
|
|
125
|
+
content: string;
|
|
126
|
+
tool_call_id?: string;
|
|
127
|
+
tool_calls?: ToolCall[];
|
|
128
|
+
}
|
|
129
|
+
interface TokenUsage {
|
|
130
|
+
promptTokens: number;
|
|
131
|
+
completionTokens: number;
|
|
132
|
+
totalTokens: number;
|
|
133
|
+
}
|
|
134
|
+
interface LlmTurnResult {
|
|
135
|
+
response?: AgentResponseType;
|
|
136
|
+
toolCalls?: ToolCall[];
|
|
137
|
+
usage?: TokenUsage;
|
|
138
|
+
}
|
|
139
|
+
interface SamplingOpts {
|
|
140
|
+
temperature?: number;
|
|
141
|
+
seed?: number;
|
|
142
|
+
timeoutMs?: number;
|
|
143
|
+
signal?: AbortSignal;
|
|
144
|
+
}
|
|
145
|
+
interface LlmAdapter {
|
|
146
|
+
executeTurn(systemPrompt: string, history: ChatMessage[], tools?: LlmToolDefinition[], responseSchema?: Record<string, unknown>, opts?: SamplingOpts): Promise<LlmTurnResult>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
interface OpenAICompatibleAdapterConfig {
|
|
150
|
+
baseUrl: string;
|
|
151
|
+
apiKey: string;
|
|
152
|
+
model: string;
|
|
153
|
+
fallbackModels?: string[];
|
|
154
|
+
temperature?: number;
|
|
155
|
+
timeoutMs?: number;
|
|
156
|
+
}
|
|
157
|
+
declare class OpenAICompatibleAdapter implements LlmAdapter {
|
|
158
|
+
private readonly cfg;
|
|
159
|
+
constructor(cfg: OpenAICompatibleAdapterConfig);
|
|
160
|
+
executeTurn(systemPrompt: string, history: ChatMessage[], tools?: LlmToolDefinition[], responseSchema?: Record<string, unknown>, opts?: SamplingOpts): Promise<LlmTurnResult>;
|
|
161
|
+
private post;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
interface BudgetConfig {
|
|
165
|
+
maxToolTurns: number;
|
|
166
|
+
maxLlmCalls: number;
|
|
167
|
+
maxRetriesPerCall: number;
|
|
168
|
+
wallClockMs: number;
|
|
169
|
+
minCallTimeoutMs: number;
|
|
170
|
+
maxCallTimeoutMs: number;
|
|
171
|
+
adaptive: boolean;
|
|
172
|
+
}
|
|
173
|
+
declare const DEFAULT_BUDGET: BudgetConfig;
|
|
174
|
+
type ExhaustReason = "wall_clock" | "llm_calls" | "tool_turns" | null;
|
|
175
|
+
interface BudgetSnapshot {
|
|
176
|
+
elapsedMs: number;
|
|
177
|
+
llmCalls: number;
|
|
178
|
+
retries: number;
|
|
179
|
+
toolTurns: number;
|
|
180
|
+
tokens: {
|
|
181
|
+
prompt: number;
|
|
182
|
+
completion: number;
|
|
183
|
+
total: number;
|
|
184
|
+
};
|
|
185
|
+
errorsByKind: Record<string, number>;
|
|
186
|
+
config: BudgetConfig;
|
|
187
|
+
}
|
|
188
|
+
declare class RunBudget {
|
|
189
|
+
readonly config: BudgetConfig;
|
|
190
|
+
private readonly now;
|
|
191
|
+
readonly startedAt: number;
|
|
192
|
+
llmCalls: number;
|
|
193
|
+
retries: number;
|
|
194
|
+
toolTurns: number;
|
|
195
|
+
promptTokens: number;
|
|
196
|
+
completionTokens: number;
|
|
197
|
+
totalTokens: number;
|
|
198
|
+
private extraToolTurns;
|
|
199
|
+
readonly errorsByKind: Record<string, number>;
|
|
200
|
+
constructor(config: BudgetConfig, now?: () => number);
|
|
201
|
+
elapsedMs(): number;
|
|
202
|
+
remainingMs(): number;
|
|
203
|
+
remainingCalls(): number;
|
|
204
|
+
private toolTurnCeiling;
|
|
205
|
+
canTakeToolTurn(): boolean;
|
|
206
|
+
grantExtraToolTurns(n: number): boolean;
|
|
207
|
+
canStartCall(): {
|
|
208
|
+
ok: boolean;
|
|
209
|
+
reason: ExhaustReason;
|
|
210
|
+
};
|
|
211
|
+
perCallTimeoutMs(): number;
|
|
212
|
+
spendCall(): void;
|
|
213
|
+
spendTokens(u?: {
|
|
214
|
+
promptTokens?: number;
|
|
215
|
+
completionTokens?: number;
|
|
216
|
+
totalTokens?: number;
|
|
217
|
+
}): void;
|
|
218
|
+
spendRetry(): void;
|
|
219
|
+
recordError(kind: string): void;
|
|
220
|
+
snapshot(): BudgetSnapshot;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
interface RetryDeps {
|
|
224
|
+
sleep?: (ms: number) => Promise<void>;
|
|
225
|
+
jitter?: () => number;
|
|
226
|
+
baseDelayMs?: number;
|
|
227
|
+
maxDelayMs?: number;
|
|
228
|
+
}
|
|
229
|
+
declare function callWithRetry<T>(fn: (timeoutMs: number) => Promise<T>, budget: RunBudget, deps?: RetryDeps): Promise<T>;
|
|
230
|
+
|
|
231
|
+
type LlmErrorKind = "rate_limit" | "server" | "timeout" | "network" | "auth" | "bad_request" | "unknown";
|
|
232
|
+
declare class LlmError extends Error {
|
|
233
|
+
readonly kind: LlmErrorKind;
|
|
234
|
+
readonly status?: number | undefined;
|
|
235
|
+
readonly retryAfterMs?: number | undefined;
|
|
236
|
+
readonly cause?: unknown | undefined;
|
|
237
|
+
constructor(kind: LlmErrorKind, message: string, status?: number | undefined, retryAfterMs?: number | undefined, cause?: unknown | undefined);
|
|
238
|
+
get retryable(): boolean;
|
|
239
|
+
}
|
|
240
|
+
declare function classifyStatus(status: number): LlmErrorKind;
|
|
241
|
+
declare function classifyThrown(e: unknown): LlmError;
|
|
242
|
+
declare function parseRetryAfter(headerVal: string | null | undefined, now?: () => number): number | undefined;
|
|
243
|
+
|
|
244
|
+
interface CanvasAction$1 {
|
|
245
|
+
action: string;
|
|
246
|
+
data: unknown;
|
|
247
|
+
message: string;
|
|
248
|
+
}
|
|
249
|
+
type RunStatus = "ok" | "partial" | "failed" | "needs_input";
|
|
250
|
+
interface RunDiagnostics {
|
|
251
|
+
status: RunStatus;
|
|
252
|
+
termination: "final_response" | "partial_actions" | "no_output" | "llm_error" | "budget_exhausted" | "clarification";
|
|
253
|
+
exhausted?: ExhaustReason;
|
|
254
|
+
lastErrorKind?: LlmErrorKind;
|
|
255
|
+
budget: BudgetSnapshot;
|
|
256
|
+
}
|
|
257
|
+
interface AgentRunResult {
|
|
258
|
+
response: AgentResponseType;
|
|
259
|
+
actions: CanvasAction$1[];
|
|
260
|
+
toolTurns: number;
|
|
261
|
+
status: RunStatus;
|
|
262
|
+
diagnostics: RunDiagnostics;
|
|
263
|
+
}
|
|
264
|
+
interface AgentExecutorOptions {
|
|
265
|
+
maxToolTurns?: number;
|
|
266
|
+
budget?: Partial<BudgetConfig>;
|
|
267
|
+
retry?: RetryDeps;
|
|
268
|
+
now?: () => number;
|
|
269
|
+
}
|
|
270
|
+
interface AgentRunInput {
|
|
271
|
+
message: string;
|
|
272
|
+
contextSummary?: string;
|
|
273
|
+
chatHistory?: ChatMessage[];
|
|
274
|
+
}
|
|
275
|
+
declare class AgentExecutor {
|
|
276
|
+
private readonly agent;
|
|
277
|
+
private readonly llm;
|
|
278
|
+
private readonly tools;
|
|
279
|
+
private readonly toolDefs;
|
|
280
|
+
private readonly allowed;
|
|
281
|
+
private readonly budgetCfg;
|
|
282
|
+
private readonly retryDeps;
|
|
283
|
+
private readonly now?;
|
|
284
|
+
constructor(agent: AgentDefinition, llm: LlmAdapter, options?: AgentExecutorOptions);
|
|
285
|
+
run(input: AgentRunInput): Promise<AgentRunResult>;
|
|
286
|
+
private settleFinal;
|
|
287
|
+
private ok;
|
|
288
|
+
private terminate;
|
|
289
|
+
private executeToolCall;
|
|
290
|
+
private finalize;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
interface CreateServerOptions {
|
|
294
|
+
llm: LlmAdapter;
|
|
295
|
+
maxToolTurns?: number;
|
|
296
|
+
budget?: Partial<BudgetConfig>;
|
|
297
|
+
agents?: AgentDefinition[];
|
|
298
|
+
}
|
|
299
|
+
declare function createServer(opts: CreateServerOptions): McpServer;
|
|
300
|
+
|
|
301
|
+
declare function specialistToolName(agent: AgentDefinition): string;
|
|
302
|
+
interface RegisterSpecialistOptions {
|
|
303
|
+
llm: LlmAdapter;
|
|
304
|
+
maxToolTurns?: number;
|
|
305
|
+
budget?: Partial<BudgetConfig>;
|
|
306
|
+
}
|
|
307
|
+
declare function registerSpecialistTool(server: McpServer, agent: AgentDefinition, opts: RegisterSpecialistOptions): void;
|
|
308
|
+
|
|
309
|
+
declare function registerSessionTools(server: McpServer): void;
|
|
310
|
+
|
|
311
|
+
declare function registerMediaTools(server: McpServer): void;
|
|
312
|
+
|
|
313
|
+
interface PlanBlock {
|
|
314
|
+
prefabId?: string;
|
|
315
|
+
blockType?: string;
|
|
316
|
+
role?: string;
|
|
317
|
+
scene?: string;
|
|
318
|
+
label?: string;
|
|
319
|
+
}
|
|
320
|
+
interface PlanScene {
|
|
321
|
+
sceneId?: string;
|
|
322
|
+
title?: string;
|
|
323
|
+
}
|
|
324
|
+
interface StructuredPlan {
|
|
325
|
+
scenes?: PlanScene[];
|
|
326
|
+
blocks: PlanBlock[];
|
|
327
|
+
}
|
|
328
|
+
interface CanvasAction {
|
|
329
|
+
action: string;
|
|
330
|
+
data: any;
|
|
331
|
+
message?: string;
|
|
332
|
+
}
|
|
333
|
+
declare function planBlockKey(b: PlanBlock): string | undefined;
|
|
334
|
+
declare function actionBlockKey(a: CanvasAction): string | undefined;
|
|
335
|
+
declare function parsePlan(raw: unknown): StructuredPlan | undefined;
|
|
336
|
+
declare function derivePlanFromText(text?: unknown): StructuredPlan | undefined;
|
|
337
|
+
interface PlanDiff {
|
|
338
|
+
missingBlocks: PlanBlock[];
|
|
339
|
+
missingScenes: number;
|
|
340
|
+
}
|
|
341
|
+
declare function diffPlan(plan: StructuredPlan, actions: CanvasAction[]): PlanDiff;
|
|
342
|
+
declare function isComplete(diff: PlanDiff): boolean;
|
|
343
|
+
declare function summarizeMissing(diff: PlanDiff): string;
|
|
344
|
+
|
|
345
|
+
interface ClaimRule {
|
|
346
|
+
id: string;
|
|
347
|
+
label: string;
|
|
348
|
+
detect: RegExp[];
|
|
349
|
+
satisfied: (actions: CanvasAction[]) => boolean;
|
|
350
|
+
}
|
|
351
|
+
declare const CLAIM_TO_OP: ClaimRule[];
|
|
352
|
+
interface UnbackedClaim {
|
|
353
|
+
id: string;
|
|
354
|
+
label: string;
|
|
355
|
+
}
|
|
356
|
+
declare function findUnbackedClaims(text: string | undefined, actions: CanvasAction[]): UnbackedClaim[];
|
|
357
|
+
|
|
358
|
+
interface SessionState {
|
|
359
|
+
activity: any;
|
|
360
|
+
activeSceneId?: string;
|
|
361
|
+
selectedBlockId?: string;
|
|
362
|
+
}
|
|
363
|
+
declare function setSessionActivity(sessionId: string, activity: any, activeSceneId?: string, selectedBlockId?: string): SessionState;
|
|
364
|
+
declare function getSession(sessionId: string | undefined): SessionState | undefined;
|
|
365
|
+
declare function clearSession(sessionId: string | undefined): void;
|
|
366
|
+
declare function applySessionActions(sessionId: string | undefined, actions: {
|
|
367
|
+
action: string;
|
|
368
|
+
data: unknown;
|
|
369
|
+
}[]): void;
|
|
370
|
+
declare function applyMutation(activity: any, action: string, data: any): void;
|
|
371
|
+
declare function buildContextString(state: SessionState): string;
|
|
372
|
+
|
|
373
|
+
declare const SERVER_NAME = "edugate-agents";
|
|
374
|
+
declare const SERVER_VERSION = "1.0.0";
|
|
375
|
+
declare const MCP_PROTOCOL_VERSION = "2025-03-26";
|
|
376
|
+
declare const MCP_ENDPOINT_PATH = "/mcp";
|
|
377
|
+
declare const DEFAULT_MAX_TOOL_TURNS = 8;
|
|
378
|
+
declare const DEFAULT_MAX_LLM_CALLS = 24;
|
|
379
|
+
declare const DEFAULT_MAX_RETRIES = 2;
|
|
380
|
+
declare const DEFAULT_WALL_CLOCK_MS = 240000;
|
|
381
|
+
declare const DEFAULT_LLM_TIMEOUT_MS = 120000;
|
|
382
|
+
|
|
383
|
+
export { AGENT_RESPONSE_JSON_SCHEMA, AgentExecutor, type AgentExecutorOptions, AgentResponseSchema, type AgentResponseType, type AgentRunInput, type AgentRunResult, type BudgetConfig, type BudgetSnapshot, CLAIM_TO_OP, type CanvasAction$1 as CanvasAction, type ChatMessage, type ClaimRule, type CreateServerOptions, DEFAULT_BUDGET, DEFAULT_LLM_TIMEOUT_MS, DEFAULT_MAX_LLM_CALLS, DEFAULT_MAX_RETRIES, DEFAULT_MAX_TOOL_TURNS, DEFAULT_WALL_CLOCK_MS, type ExhaustReason, type LlmAdapter, LlmError, type LlmErrorKind, type LlmToolDefinition, type LlmTurnResult, MCP_ENDPOINT_PATH, MCP_PROTOCOL_VERSION, OpenAICompatibleAdapter, type OpenAICompatibleAdapterConfig, type CanvasAction as PlanCanvasAction, type RegisterSpecialistOptions, type RetryDeps, RunBudget, type RunDiagnostics, type RunStatus, SERVER_NAME, SERVER_VERSION, type SamplingOpts, type SessionState, type TokenUsage, type ToolCall, type UnbackedClaim, actionBlockKey, applyMutation, applySessionActions, buildContextString, callWithRetry, classifyStatus, classifyThrown, clearSession, createServer, derivePlanFromText, diffPlan, findUnbackedClaims, getSession, isComplete, parsePlan, parseRetryAfter, planBlockKey, registerMediaTools, registerSessionTools, registerSpecialistTool, setSessionActivity, specialistToolName, summarizeMissing };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";var ce=Object.defineProperty;var Ke=Object.getOwnPropertyDescriptor;var Fe=Object.getOwnPropertyNames;var qe=Object.prototype.hasOwnProperty;var We=(e,t)=>{for(var s in t)ce(e,s,{get:t[s],enumerable:!0})},ae=(e,t,s,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Fe(t))!qe.call(e,n)&&n!==s&&ce(e,n,{get:()=>t[n],enumerable:!(o=Ke(t,n))||o.enumerable});return e},A=(e,t,s)=>(ae(e,t,"default"),s&&ae(s,t,"default"));var Ve=e=>ae(ce({},"__esModule",{value:!0}),e);var S={};We(S,{AGENT_RESPONSE_JSON_SCHEMA:()=>W,AgentExecutor:()=>M,AgentResponseSchema:()=>q,CLAIM_TO_OP:()=>je,DEFAULT_BUDGET:()=>V,DEFAULT_LLM_TIMEOUT_MS:()=>Vt,DEFAULT_MAX_LLM_CALLS:()=>Ft,DEFAULT_MAX_RETRIES:()=>qt,DEFAULT_MAX_TOOL_TURNS:()=>Kt,DEFAULT_WALL_CLOCK_MS:()=>Wt,LlmError:()=>k,MCP_ENDPOINT_PATH:()=>at,MCP_PROTOCOL_VERSION:()=>rt,OpenAICompatibleAdapter:()=>F,RunBudget:()=>O,SERVER_NAME:()=>ue,SERVER_VERSION:()=>de,actionBlockKey:()=>be,applyMutation:()=>j,applySessionActions:()=>ye,buildContextString:()=>$,callWithRetry:()=>G,classifyStatus:()=>U,classifyThrown:()=>C,clearSession:()=>ge,createServer:()=>gt,derivePlanFromText:()=>ee,diffPlan:()=>X,findUnbackedClaims:()=>ke,getSession:()=>D,isComplete:()=>Q,parsePlan:()=>Z,parseRetryAfter:()=>K,planBlockKey:()=>B,registerMediaTools:()=>Se,registerSessionTools:()=>xe,registerSpecialistTool:()=>Te,setSessionActivity:()=>me,specialistToolName:()=>ze,summarizeMissing:()=>z});module.exports=Ve(S);var Ge=new Set(["rate_limit","server","timeout","network"]),k=class extends Error{constructor(s,o,n,i,r){super(o);this.kind=s;this.status=n;this.retryAfterMs=i;this.cause=r;this.name="LlmError"}kind;status;retryAfterMs;cause;get retryable(){return Ge.has(this.kind)}};function U(e){return e===429?"rate_limit":e>=500?"server":e===401||e===403?"auth":e===400||e===404||e===413||e===422?"bad_request":"unknown"}function C(e){if(e instanceof k)return e;let t=e?.name,s=String(e?.message??e);return t==="AbortError"||/\baborted\b|timed? ?out|timeout/i.test(s)?new k("timeout",s,void 0,void 0,e):/fetch failed|ECONNRESET|ENOTFOUND|ECONNREFUSED|EAI_AGAIN|socket hang up|network|ETIMEDOUT/i.test(s)?new k("network",s,void 0,void 0,e):new k("unknown",s,void 0,void 0,e)}function K(e,t=Date.now){if(!e)return;let s=String(e).trim();if(s==="")return;let o=Number(s);if(Number.isFinite(o))return Math.max(0,Math.round(o*1e3));let n=Date.parse(s);if(!Number.isNaN(n))return Math.max(0,n-t())}var F=class{constructor(t){this.cfg=t}cfg;async executeTurn(t,s,o,n,i){let r=[{role:"system",content:t},...s.map(Xe)],a={model:this.cfg.model,messages:r,temperature:i?.temperature??this.cfg.temperature??.2};this.cfg.fallbackModels&&this.cfg.fallbackModels.length>0&&(a.models=[this.cfg.model,...this.cfg.fallbackModels]),i?.seed!==void 0&&(a.seed=i.seed),o&&o.length>0?(a.tools=o.map(b=>({type:"function",function:{name:b.name,description:b.description,parameters:b.parameters}})),a.tool_choice="auto"):n?a.response_format={type:"json_schema",json_schema:{name:"agent_response",strict:!0,schema:n}}:a.response_format={type:"json_object"};let c=await this.post(a,i),f=c?.choices?.[0]?.message??{},m=Ye(c?.usage),d=f.tool_calls;if(d&&d.length>0){let b=d.map((l,g)=>({id:l.id||`call_${g}`,type:"function",function:{name:l.function.name,arguments:l.function.arguments||"{}"}})),u=ve(f.content);return{toolCalls:b,response:u??void 0,usage:m}}return{response:ve(f.content)??{text:typeof f.content=="string"?f.content:""},usage:m}}async post(t,s){let o=`${this.cfg.baseUrl.replace(/\/$/,"")}/chat/completions`,n=new AbortController,i=s?.timeoutMs??this.cfg.timeoutMs??12e4,r=setTimeout(()=>n.abort(),i),a=()=>n.abort();s?.signal&&(s.signal.aborted?n.abort():s.signal.addEventListener("abort",a,{once:!0}));try{let c=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",...this.cfg.apiKey?{Authorization:`Bearer ${this.cfg.apiKey}`}:{}},body:JSON.stringify(t),signal:n.signal});if(!c.ok){let p=await c.text().catch(()=>"");throw new k(U(c.status),`LLM request failed (${c.status} ${c.statusText}): ${p.slice(0,500)}`,c.status,K(c.headers.get("retry-after")))}return await c.json()}catch(c){throw C(c)}finally{clearTimeout(r),s?.signal&&s.signal.removeEventListener("abort",a)}}};function Ye(e){if(!e||typeof e!="object")return;let t=e,s=r=>typeof r=="number"&&Number.isFinite(r)?r:0,o=s(t.prompt_tokens),n=s(t.completion_tokens),i=s(t.total_tokens)||o+n;if(!(!i&&!o&&!n))return{promptTokens:o,completionTokens:n,totalTokens:i}}function Xe(e){let t={role:e.role,content:e.content};return e.tool_call_id&&(t.tool_call_id=e.tool_call_id),e.tool_calls&&(t.tool_calls=e.tool_calls),t}function ve(e){if(typeof e!="string"||e.trim()==="")return null;let t=e.trim();try{return JSON.parse(t)}catch{let s=t.indexOf("{"),o=t.lastIndexOf("}");if(s!==-1&&o>s)try{return JSON.parse(t.slice(s,o+1))}catch{}return null}}var y=require("zod"),q=y.z.object({text:y.z.string().describe("A SHORT introductory or confirmation text (max 2 sentences). DO NOT include bullet points, DO NOT include links, DO NOT include options here. If there are options or search results, you MUST use the 'choices' field."),actionMeta:y.z.object({type:y.z.string().describe("The kind of operation performed, e.g. addBlock, updateBlockTheme, configureTimer, manageVariables, none."),targetId:y.z.string().nullable().optional().describe("The ID of the scene or block you are operating on. Omit for activity-level operations (e.g. global variables or activity timer).")}).optional().describe("Optional. IMPORTANT: You must ALWAYS call the tools! You cannot execute actions just by returning this JSON."),choices:y.z.array(y.z.object({question:y.z.string().describe("The question regarding the doubt to be resolved or the options proposed to the user."),elementId:y.z.string().describe("The ID of the block or scene to which this specific choice refers.").optional().nullable(),options:y.z.array(y.z.string()).describe("List of fixed options (2 to 4) to choose from. DO NOT include 'Other' here.")})).optional().describe("Guided multiple choice groups. The frontend will show these options + a fixed text input for the 'Other' option."),revertSnapshot:y.z.any().optional().describe("Optional. The previous state of the activity."),delegateTo:y.z.string().nullable().optional().describe("Used ONLY by the general assistant. When the user's request actually requires a specialist (structural, content, styling, action), set this to that agent's id so the orchestrator re-routes. Leave null/omit for pure conversation.")}),W={type:"object",additionalProperties:!1,required:["text"],properties:{text:{type:"string"},actionMeta:{type:"object",additionalProperties:!1,required:["type"],properties:{type:{type:"string"},targetId:{type:["string","null"]}}},choices:{type:"array",items:{type:"object",additionalProperties:!1,required:["question","options"],properties:{question:{type:"string"},elementId:{type:["string","null"]},options:{type:"array",items:{type:"string"}}}}},delegateTo:{type:["string","null"]}}};var _e=require("@edugate/authoring/canvas"),Y=require("@edugate/authoring/canvas");var V={maxToolTurns:8,maxLlmCalls:24,maxRetriesPerCall:2,wallClockMs:24e4,minCallTimeoutMs:15e3,maxCallTimeoutMs:12e4,adaptive:!0};function Ie(e){let t=e??"",s=[...t.matchAll(/\b(\d{1,2})\b/g)].map(i=>Number(i[1])).filter(i=>i>=2&&i<=30),o=s.length?Math.max(...s):0,n=(t.match(/[,;]|\b(?:e|and)\b|\n\s*[-*\d]/gi)??[]).length;return Math.max(1,o,Math.ceil(n/2))}function Re(e,t){if(t<=1)return e;let o=t*2+2,n=Math.min(e.maxToolTurns*4,Math.max(e.maxToolTurns,o)),i=Math.min(e.maxLlmCalls*4,Math.max(e.maxLlmCalls,n*2+4));return{...e,maxToolTurns:n,maxLlmCalls:i}}var O=class{constructor(t,s=Date.now){this.config=t;this.now=s;this.startedAt=s()}config;now;startedAt;llmCalls=0;retries=0;toolTurns=0;promptTokens=0;completionTokens=0;totalTokens=0;extraToolTurns=0;errorsByKind={};elapsedMs(){return this.now()-this.startedAt}remainingMs(){return Math.max(0,this.config.wallClockMs-this.elapsedMs())}remainingCalls(){return Math.max(0,this.config.maxLlmCalls-this.llmCalls)}toolTurnCeiling(){return Math.min(this.config.maxToolTurns+this.extraToolTurns,this.config.maxToolTurns*2)}canTakeToolTurn(){return this.toolTurns<this.toolTurnCeiling()}grantExtraToolTurns(t){return this.toolTurns>=this.config.maxToolTurns*2?!1:(this.extraToolTurns+=t,!0)}canStartCall(){return this.remainingMs()<=0?{ok:!1,reason:"wall_clock"}:this.remainingCalls()<=0?{ok:!1,reason:"llm_calls"}:{ok:!0,reason:null}}perCallTimeoutMs(){return Math.max(this.config.minCallTimeoutMs,Math.min(this.config.maxCallTimeoutMs,this.remainingMs()))}spendCall(){this.llmCalls++}spendTokens(t){t&&(this.promptTokens+=t.promptTokens??0,this.completionTokens+=t.completionTokens??0,this.totalTokens+=t.totalTokens??(t.promptTokens??0)+(t.completionTokens??0))}spendRetry(){this.retries++}recordError(t){this.errorsByKind[t]=(this.errorsByKind[t]??0)+1}snapshot(){return{elapsedMs:this.elapsedMs(),llmCalls:this.llmCalls,retries:this.retries,toolTurns:this.toolTurns,tokens:{prompt:this.promptTokens,completion:this.completionTokens,total:this.totalTokens},errorsByKind:{...this.errorsByKind},config:this.config}}};var Qe=e=>new Promise(t=>setTimeout(t,e));async function G(e,t,s={}){let o=s.sleep??Qe,n=s.jitter??Math.random,i=s.baseDelayMs??500,r=s.maxDelayMs??8e3,a=0,c;for(;;){let p=t.canStartCall();if(!p.ok)throw c??new k("timeout",`request budget exhausted (${p.reason}) before a successful LLM call`);t.spendCall();try{return await e(t.perCallTimeoutMs())}catch(f){let m=C(f);if(t.recordError(m.kind),c=m,!m.retryable||a>=t.config.maxRetriesPerCall)throw m;let d=Math.min(r,i*2**a),h=m.retryAfterMs??Math.floor(d*n());if(t.remainingMs()-h<t.config.minCallTimeoutMs)throw m;t.spendRetry(),a++,h>0&&await o(h)}}}var we=W,Ze=3,et="Non hai ancora completato tutti gli elementi richiesti. Continua con quelli mancanti chiamando i tool necessari (una chiamata diversa per ogni elemento). Restituisci il JSON finale SOLO quando l'intera richiesta \xE8 stata soddisfatta.",tt=new Set(["x","y"]);function le(e){return Array.isArray(e)?e.map(le):e&&typeof e=="object"?Object.fromEntries(Object.entries(e).filter(([t])=>!tt.has(t)).sort(([t],[s])=>t.localeCompare(s)).map(([t,s])=>[t,le(s)])):e}function Oe(e,t){try{return`${e}:${JSON.stringify(le(JSON.parse(t)))}`}catch{return`${e}:${t}`}}function _(e,t,s){return JSON.stringify({status:"error",code:e,message:t,...s?{errors:s}:{}})}function nt(e){let t=e.map(o=>o.message).filter(o=>!!o);if(e.length===0)return"Nessuna modifica applicata.";if(e.length===1)return t[0]??"Modifica applicata.";let s=t.slice(0,4).join(" ");return`Ho applicato ${e.length} modifiche. ${s}`.trim()}function ot(e,t){if(e)switch(e.kind){case"rate_limit":return"Il servizio AI \xE8 momentaneamente sovraccarico (rate limit). Riprova tra qualche secondo.";case"timeout":return"Il servizio AI non ha risposto in tempo. Riprova; se usi un modello on-prem lento, aumenta il timeout.";case"server":case"network":return"Il servizio AI non \xE8 raggiungibile in questo momento. Riprova tra poco.";case"auth":return"Configurazione del servizio AI non valida (autenticazione). Contatta un amministratore.";case"bad_request":return"La richiesta al modello non \xE8 valida per questo provider. Potrebbe non supportare i tool o lo schema richiesto.";default:return"Si \xE8 verificato un errore con il servizio AI. Riprova."}return t==="wall_clock"||t==="llm_calls"?"Non sono riuscito a completare la richiesta entro il budget disponibile. Prova a semplificare o a ripetere la richiesta.":"Non sono riuscito a eseguire la richiesta: il modello non ha prodotto un'azione valida. Riprova o riformula la richiesta."}var M=class{constructor(t,s,o={}){this.agent=t;this.llm=s;this.tools=(0,_e.resolveTools)(t.toolNames),this.toolDefs=this.tools.map(n=>({name:n.name,description:n.description,parameters:(0,Y.toolParametersSchema)(n.inputShape)})),this.allowed=new Set(t.toolNames),this.budgetCfg={...V,...o.maxToolTurns!==void 0?{maxToolTurns:o.maxToolTurns}:{},...o.budget??{}},this.retryDeps=o.retry??{},this.now=o.now}agent;llm;tools;toolDefs;allowed;budgetCfg;retryDeps;now;async run(t){let s=this.budgetCfg.adaptive===!1?this.budgetCfg:Re(this.budgetCfg,Ie(t.message)),o=new O(s,this.now),n=new Map(this.tools.map(u=>[u.name,u])),i=this.toolDefs.length>0,r=t.contextSummary?`${t.message}
|
|
2
|
+
${t.contextSummary}`:t.message,a=[...t.chatHistory??[],{role:"user",content:r}],c=[],p=new Set,f,m=async(u,l)=>{let g=await G(v=>this.llm.executeTurn(this.agent.systemPrompt,a,u,l,{timeoutMs:v}),o,this.retryDeps);return o.spendTokens(g.usage),g},d=async()=>{for(;o.canTakeToolTurn()&&o.canStartCall().ok;){let u;try{u=await m(i?this.toolDefs:void 0,i?void 0:we)}catch(l){return f=C(l),null}if(u.toolCalls&&u.toolCalls.length>0){o.toolTurns++,a.push({role:"assistant",content:u.response?.text??"",tool_calls:u.toolCalls});let l=!0;for(let g of u.toolCalls)if(!p.has(Oe(g.function.name,g.function.arguments))){l=!1;break}for(let g of u.toolCalls)await this.executeToolCall(g,n,a,c,p);if(l)return null;continue}if(u.response){let l=this.finalize(u.response);if(l)return this.settleFinal(l,c,o)}return null}return null},h=await d();if(h)return h;let b=0;for(;s.adaptive!==!1&&!f&&c.length>0&&b<Ze&&!o.canTakeToolTurn()&&o.canStartCall().ok&&o.grantExtraToolTurns(2);)if(b++,a.push({role:"user",content:et}),h=await d(),h)return h;if(!f&&o.canStartCall().ok){a.push({role:"user",content:"You have reached the maximum number of tool calls. Provide your final response now as JSON."});try{let u=await m(void 0,we),l=u.response?this.finalize(u.response):null;if(l)return this.settleFinal(l,c,o)}catch(u){f=C(u)}}return this.terminate(c,o,f)}settleFinal(t,s,o){if(!(Array.isArray(t.choices)&&t.choices.length>0))return this.ok(t,s,o);let i={status:"needs_input",termination:"clarification",budget:o.snapshot()};return{response:t,actions:s,toolTurns:o.toolTurns,status:"needs_input",diagnostics:i}}ok(t,s,o){let n={status:"ok",termination:"final_response",budget:o.snapshot()};return{response:t,actions:s,toolTurns:o.toolTurns,status:"ok",diagnostics:n}}terminate(t,s,o){let n=s.canStartCall(),i=n.ok?null:n.reason;if(t.length>0){let a={status:"partial",termination:o?"llm_error":i?"budget_exhausted":"partial_actions",exhausted:i??void 0,lastErrorKind:o?.kind,budget:s.snapshot()};return{response:{text:nt(t)},actions:t,toolTurns:s.toolTurns,status:"partial",diagnostics:a}}let r={status:"failed",termination:o?"llm_error":i?"budget_exhausted":"no_output",exhausted:i??void 0,lastErrorKind:o?.kind,budget:s.snapshot()};return{response:{text:ot(o,i)},actions:t,toolTurns:s.toolTurns,status:"failed",diagnostics:r}}async executeToolCall(t,s,o,n,i){let r=Oe(t.function.name,t.function.arguments);if(i.has(r)){o.push({role:"tool",tool_call_id:t.id,content:_("DUPLICATE","Duplicate call blocked. Provide your final response now.")});return}if(i.add(r),!this.allowed.has(t.function.name)){o.push({role:"tool",tool_call_id:t.id,content:_("NOT_ALLOWED",`Tool "${t.function.name}" is not available for this agent.`)});return}let a=s.get(t.function.name);if(!a){o.push({role:"tool",tool_call_id:t.id,content:_("UNKNOWN_TOOL",`Tool "${t.function.name}" does not exist.`)});return}let c;try{c=JSON.parse(t.function.arguments||"{}")}catch{o.push({role:"tool",tool_call_id:t.id,content:_("BAD_JSON",`Arguments for "${t.function.name}" were not valid JSON.`)});return}let p=(0,Y.validateToolInput)(a.inputShape,c);if(!p.ok){o.push({role:"tool",tool_call_id:t.id,content:_("INVALID_ARGS",`Invalid arguments for "${t.function.name}". Fix them and re-emit the call.`,p.errors)});return}try{let m=(await a.handler(p.value)).content[0]?.text??"{}",d=st(m);d?.status==="success"&&typeof d.action=="string"?(n.push({action:d.action,data:d.data,message:d.message??""}),o.push({role:"tool",tool_call_id:t.id,content:this.agent.name==="content"?m:it(d)})):o.push({role:"tool",tool_call_id:t.id,content:m})}catch(f){o.push({role:"tool",tool_call_id:t.id,content:_("TOOL_ERROR",f?.message??String(f))})}}finalize(t){let s=q.safeParse(t);if(s.success)return s.data;let o=typeof t?.text=="string"?t.text.trim():"";return o.length>0?{text:o}:null}};function st(e){try{return JSON.parse(e)}catch{return null}}function it(e){let t=e&&e.data||{},s=t?.block?.id??t?.id??t?.blockId??t?.sceneId??void 0,o={status:"success",action:e.action};return s!==void 0&&(o.id=s),t?.sceneId&&t.sceneId!==s&&(o.sceneId=t.sceneId),e.message&&(o.message=e.message),JSON.stringify(o)}var He=require("@modelcontextprotocol/sdk/server/mcp.js"),Ue=require("@edugate/authoring/agents");var ue="edugate-agents",de="1.0.0",rt="2025-03-26",at="/mcp",Kt=8,Ft=24,qt=2,Wt=24e4,Vt=12e4;var L=require("zod");var fe=new Map;function Ee(e){return e==null?e:JSON.parse(JSON.stringify(e))}function me(e,t,s,o){let n={activity:Ee(t)??{scenes:[]},activeSceneId:s,selectedBlockId:o};return Array.isArray(n.activity.scenes)||(n.activity.scenes=[]),fe.set(e,n),n}function D(e){if(e)return fe.get(e)}function ge(e){e&&fe.delete(e)}function ye(e,t){let s=D(e);if(s)for(let o of t)try{j(s.activity,o.action,o.data)}catch{}}function N(e,t){return(e.scenes||[]).find(s=>s.id===t)}function x(e,t){for(let s of e.scenes||[]){let o=(s.blocks||[]).find(n=>n.id===t);if(o)return o}}function j(e,t,s){if(!e||typeof e!="object")return;Array.isArray(e.scenes)||(e.scenes=[]);let o=e.scenes,n=s||{};switch(t){case"createScene":{let{newIndex:i,...r}=n;r.blocks||(r.blocks=[]);let a=typeof i=="number"?pe(i,o.length):o.length;o.splice(a,0,r);break}case"updateScene":{let i=N(e,n.sceneId);if(!i)break;n.updates&&typeof n.updates=="object"&&Object.assign(i,n.updates),typeof n.newIndex=="number"&&Me(o,i,n.newIndex);break}case"deleteScene":{let i=o.findIndex(r=>r.id===n.sceneId);i>=0&&o.splice(i,1);break}case"reorderScenes":{let i=N(e,n.sceneId);i&&typeof n.newIndex=="number"&&Me(o,i,n.newIndex);break}case"addBlock":{let i=N(e,n.sceneId);if(!i||!n.block)break;Array.isArray(i.blocks)||(i.blocks=[]),i.blocks.push(n.block);break}case"updateBlock":{let i=n.sceneId?E(e,n.sceneId,n.blockId):x(e,n.blockId);i&&n.updates&&typeof n.updates=="object"&&ct(i,n.updates);break}case"updateBlockTheme":{let i=E(e,n.sceneId,n.blockId)||x(e,n.blockId);i&&n.theme&&(i.theme=Ne(i.theme||{},n.theme));break}case"updateBlockElementStyle":{let i=E(e,n.sceneId,n.blockId)||x(e,n.blockId);i&&(i.elementStyles=i.elementStyles||{},n.elementId&&(i.elementStyles[n.elementId]={...i.elementStyles[n.elementId]||{},...n.customStyle||{}}));break}case"deleteBlock":{let i=N(e,n.sceneId);if(!i||!Array.isArray(i.blocks))break;let r=i.blocks.findIndex(a=>a.id===n.blockId);r>=0&&i.blocks.splice(r,1);break}case"duplicateBlock":{let i=N(e,n.sceneId);if(!i||!Array.isArray(i.blocks))break;let r=i.blocks.find(a=>a.id===n.blockId);if(r){let a=Ee(r);a.id=`${r.id}-copy-${i.blocks.length}`,i.blocks.push(a)}break}case"groupBlocks":{let i=Array.isArray(n.blockIds)?n.blockIds:[],r=`group-${i.join("-").slice(0,24)}`;for(let a of i){let c=x(e,a);c&&(c.groupId=r)}break}case"addAction":{let i=E(e,n.sceneId,n.blockId)||x(e,n.blockId);i&&n.action&&(i.actions=Array.isArray(i.actions)?i.actions:[],i.actions.push(n.action));break}case"removeAction":{let i=E(e,n.sceneId,n.blockId)||x(e,n.blockId);i&&Array.isArray(i.actions)&&(i.actions=i.actions.filter(r=>r&&r.id!==n.actionId));break}case"manageVariables":{e.variables=Array.isArray(e.variables)?e.variables:[];let i=n.operation;if(i==="add"||i==="update"){let r=e.variables.find(a=>a.name===n.name);r?r.defaultValue=n.defaultValue:e.variables.push({name:n.name,defaultValue:n.defaultValue})}else i==="remove"&&(e.variables=e.variables.filter(r=>r.name!==n.name));break}case"configureTimer":{if(n.scope==="block"){let i=E(e,n.sceneId,n.blockId)||x(e,n.blockId);i&&(i.hasTimer=n.hasTimer??!0,i.timer=n.timer)}else e.hasTimer=n.hasTimer??!0,e.timer=n.timer;break}case"insertItem":{let i=x(e,n.blockId);if(i&&n.collection&&n.item){i[n.collection]=Array.isArray(i[n.collection])?i[n.collection]:[];let r=typeof n.at=="number"?pe(n.at,i[n.collection].length):i[n.collection].length;i[n.collection].splice(r,0,n.item)}break}case"updateItem":{let i=x(e,n.blockId);i&&Array.isArray(i[n.collection])&&(i[n.collection]=i[n.collection].map(r=>r&&typeof r=="object"&&r.id===n.itemId?{...r,...n.item||{}}:r));break}case"deleteItem":{let i=x(e,n.blockId);i&&Array.isArray(i[n.collection])&&(i[n.collection]=i[n.collection].filter(r=>!(r&&typeof r=="object"&&r.id===n.itemId)));break}default:break}}function E(e,t,s){let o=N(e,t);if(!(!o||!Array.isArray(o.blocks)))return o.blocks.find(n=>n.id===s)}function ct(e,t){for(let[s,o]of Object.entries(t))o&&typeof o=="object"&&!Array.isArray(o)&&e[s]&&typeof e[s]=="object"?e[s]={...e[s],...o}:e[s]=o}function Ne(e,t){let s={...e||{}};for(let[o,n]of Object.entries(t||{}))s[o]=n&&typeof n=="object"&&!Array.isArray(n)?Ne(s[o]||{},n):n;return s}function pe(e,t){return Math.max(0,Math.min(e,t))}function Me(e,t,s){let o=e.indexOf(t);o<0||(e.splice(o,1),e.splice(pe(s,e.length),0,t))}function $(e){let t=e.activity||{},s=t.scenes||[],o=(a,c)=>{let p=a.position?`, Position: {x: ${Math.round(a.position.x)}, y: ${Math.round(a.position.y)}} (0-100%)`:"",f=a.size?`, Size: {width: ${a.size.width}, height: ${a.size.height}} (px)`:"",m=a.id===e.selectedBlockId?" \u2605SELECTED":"";return`[Block ${c+1}: ID: ${a.id}, Type: ${a.type}, Title: "${a.title}"${p}${f}${m}]`},n=(a,c)=>{let p=a.blocks&&a.blocks.length?a.blocks.map(o).join(", "):"No blocks";return`- Scene ${c+1}: Title "${a.title}", ID: "${a.id}"
|
|
3
|
+
Blocks: ${p}`},i=e.selectedBlockId?x(t,e.selectedBlockId):void 0,r=i?`
|
|
4
|
+
Selected block content (ID: ${i.id}, Type: ${i.type}) \u2014 when reconfiguring, PRESERVE these items and add to them:
|
|
5
|
+
${JSON.stringify(i)}`:"";return`
|
|
6
|
+
[CONTEXT] Activity: "${t.title||"Untitled"}"
|
|
7
|
+
Scenes list (numbered 1-based):
|
|
8
|
+
${s.length?s.map(n).join(`
|
|
9
|
+
`):"None"}
|
|
10
|
+
Active scene ID: ${e.activeSceneId||"none"}.`+r+`
|
|
11
|
+
Block positions are already percentages (0-100) of the scene canvas; no separate canvas dimensions are provided \u2014 use them as-is.
|
|
12
|
+
When the user references a scene/block by number, resolve it to its ID from this list. If the user does NOT specify a scene, operate on the Active scene ID. Every scene/block listed above EXISTS \u2014 act on it by its ID; never ask the user for an ID that is present here.`}var Be=require("@edugate/authoring/prefabs"),he=require("@edugate/authoring/canvas");function Le(e,t){let s=e??(t?Be.PREFABS_BY_ID[t]?.blockType:void 0);return s?String(s).trim().toLowerCase():void 0}function B(e){return Le(e.blockType,e.prefabId)}function be(e){if(e.action==="addBlock")return Le(e.data?.block?.type,e.data?.prefabId)}function Z(e){let t=e;if(typeof e=="string"){if(!e.trim())return;try{t=JSON.parse(e)}catch{return}}if(!t||typeof t!="object")return;let s=[],o;if(Array.isArray(t.blocks))s=t.blocks.filter(n=>n&&typeof n=="object");else if(Array.isArray(t.scenes)){o=[];for(let n of t.scenes)if(!(!n||typeof n!="object")&&(o.push({sceneId:n.sceneId,title:n.title}),Array.isArray(n.blocks)))for(let i of n.blocks)i&&typeof i=="object"&&s.push({...i,scene:i.scene??n.sceneId})}if(Array.isArray(t.scenes)&&!o&&(o=t.scenes.filter(n=>n&&typeof n=="object").map(n=>({sceneId:n.sceneId,title:n.title}))),s=s.filter(n=>B(n)!==void 0),!(s.length===0&&(!o||o.length===0)))return{blocks:s,scenes:o}}var De={grafico:"Chart",immagine:"Media",immagini:"Media",foto:"Media",sondaggio:"Quiz",cruciverba:"Crossword",memoria:"Memory",ruota:"SpinningWheel",dado:"Dice"};function ee(e){if(typeof e!="string"||!e.trim())return;let t=[...he.ALL_BLOCK_TYPES,...Object.keys(De)],s=new RegExp("\\b("+t.join("|")+")\\b","gi"),o=[],n;for(;(n=s.exec(e))!==null;){let i=n[1].toLowerCase(),r=he.ALL_BLOCK_TYPES.find(a=>a.toLowerCase()===i)??De[i];r&&o.push({blockType:r})}return o.length>=2?{blocks:o}:void 0}function X(e,t){let s=new Map;for(let a of t){let c=be(a);c&&s.set(c,(s.get(c)??0)+1)}let o=[];for(let a of e.blocks){let c=B(a);if(!c)continue;let p=s.get(c)??0;p>0?s.set(c,p-1):o.push(a)}let n=e.scenes?.length??0,i=t.filter(a=>a.action==="createScene").length,r=Math.max(0,n-i);return{missingBlocks:o,missingScenes:r}}function Q(e){return e.missingBlocks.length===0&&e.missingScenes===0}function z(e){let t=[];e.missingScenes>0&&t.push(`${e.missingScenes} scena/e ancora da creare`);for(let s of e.missingBlocks)t.push(s.label?`${s.label} (${B(s)})`:B(s));return t.join(", ")}async function Pe(e){let t=e.maxContinuations??2,s=[...e.initialActions],o=X(e.plan,s),n=0;for(;!Q(o)&&n<t;){let i=o.missingBlocks.length+o.missingScenes,r=await e.runContinuation(o,s);n++,r.actions.length&&s.push(...r.actions),o=X(e.plan,s);let a=o.missingBlocks.length+o.missingScenes;if(r.status!=="ok"&&r.status!=="success"||a>=i)break}return{actions:s,diff:o,continuations:n,complete:Q(o)}}var te=(e,...t)=>e.some(s=>t.includes(s.action)),je=[{id:"timer",label:"il timer",detect:[/\b(imposta|impost|configur|aggiun|attiv|cre|sett|mett|mess|abilit)[a-z]*\s+(?:un[oa]?\s+|il\s+|lo\s+|la\s+|del\s+)?timer\b/i,/\btimer\b[^.!?\n]{0,25}?\b\d+\s*(second|minut|sec\b|min\b)/i,/\btimer\b\s*(?:di|da|a|of|for|to)\s*\d+/i,/\b(set|add|configur|enabl|creat)[a-z]*\s+(?:a\s+|the\s+)?timer\b/i],satisfied:e=>te(e,"configureTimer")},{id:"variable",label:"la variabile",detect:[/\b(cre|aggiun|impost|impost|defin|configur|introdott|settat)[a-z]*\s+(?:una\s+|la\s+|il\s+|un[oa]?\s+|nuov[ao]\s+)*variabil[ei]\b/i,/\b(creat|add|set|defin|configur|introduc)[a-z]*\s+(?:a\s+|the\s+|new\s+)*variable[s]?\b/i],satisfied:e=>te(e,"manageVariables")||e.some(t=>t.action==="addAction"&&typeof t.data=="object"&&t.data!==null&&["set_variable","math_variable"].includes(t.data.action?.type??""))},{id:"score",label:"il punteggio",detect:[/\b(imposta|impost|assegn|configur|aggiun|sett|attribu|dat[oa]|val[a-z]*)[a-z]*\s+(?:un\s+|il\s+|lo\s+|di\s+)?punteggi[oa]\b/i,/\bpunteggi[oa]\b[^.!?\n]{0,20}?\b\d+\b/i,/\b(set|assign|configur|add|award|give[ns]?)[a-z]*\s+(?:a\s+|the\s+)?score\b/i],satisfied:e=>e.some(t=>t.action==="updateBlock"&&typeof t.data=="object"&&t.data!==null&&Object.prototype.hasOwnProperty.call(t.data.updates??{},"score"))||te(e,"addBlock")},{id:"background",label:"lo sfondo",detect:[/\b(cambi|imposta|impost|modific|aggiorn|sett|mett|mess|applicat|colorat)[a-z]*\s+(?:lo\s+|il\s+|un\s+|nuovo\s+)?sfondo\b/i,/\b(chang|set|updat|appl|creat)[a-z]*\s+(?:the\s+|a\s+|new\s+)?background\b/i],satisfied:e=>te(e,"updateScene","updateBlockTheme")}];function ke(e,t){if(typeof e!="string"||!e.trim())return[];let s=[];for(let o of je)o.detect.some(i=>i.test(e))&&!o.satisfied(t)&&s.push({id:o.id,label:o.label});return s}function lt(e){return e.toolNames.includes("add_block")}function ze(e){return`${e.name}_agent`}var ut={message:L.z.string().describe("The user's natural-language instruction for this specialist (in any language)."),activityContext:L.z.string().optional().describe("The current activity state as a JSON STRING (scenes, blocks, selection, variables). Passed as a string (not an object) for Dify Tool-node compatibility; the server parses it. Optional \u2014 the per-session state seeded by set_activity is preferred when present. In Dify map to the `activity_context` Start input."),contextSummary:L.z.string().optional().describe("Optional pre-rendered [CONTEXT] block. If provided it is used verbatim instead of parsing activityContext."),chatHistory:L.z.string().optional().describe('Optional prior conversation turns as a JSON STRING array: [{"role":"user|assistant","content":"..."}]. Passed as a string (not an array) for Dify Tool-node compatibility; the server parses it.'),plan:L.z.string().optional().describe('Optional STRUCTURED build plan as a JSON STRING: {"scenes":[{"sceneId":"scene-1","title":"..."}],"blocks":[{"prefabId":"quiz.singlechoice","scene":"scene-1","role":"assessment","label":"..."}]}. When present, the server verifies the build against it by prefab/type IDENTITY and continues building only the specific missing items. Passed as a string (not an object) for Dify Tool-node compatibility. In Dify bind to the persisted `conv_plan` from the planner turn.')};function dt(e){if(e&&typeof e=="object"&&!Array.isArray(e))return e;if(typeof e=="string"&&e.trim())try{let t=JSON.parse(e);return t&&typeof t=="object"&&!Array.isArray(t)?t:void 0}catch{return}}function $e(e){let t=typeof e=="string"&&e.trim()?pt(e):e;if(!Array.isArray(t))return;let s=t.filter(o=>!!o&&typeof o=="object"&&typeof o.role=="string"&&typeof o.content=="string");return s.length?s:void 0}function pt(e){try{return JSON.parse(e)}catch{return}}function ft(e){if(e.contextSummary&&e.contextSummary.trim())return e.contextSummary;if(e.activityContext)return`[CONTEXT]
|
|
13
|
+
${JSON.stringify(e.activityContext)}`}function Te(e,t,s){let o=new M(t,s.llm,{maxToolTurns:s.maxToolTurns,budget:s.budget});e.registerTool(ze(t),{title:t.card.name,description:t.card.description,inputSchema:ut,annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0}},async(n,i)=>{let r=i?.sessionId,a=D(r),c=dt(n.activityContext),p=c?.activity,f=c&&Array.isArray(c.scenes)?c:void 0,m=typeof n.activityContext=="string"&&n.activityContext.trim().startsWith("[CONTEXT]")?n.activityContext:void 0,d=p??f??a?.activity,h=(typeof c?.activeSceneId=="string"?c.activeSceneId:void 0)??a?.activeSceneId,b=(typeof c?.selectedBlockId=="string"?c.selectedBlockId:void 0)??a?.selectedBlockId,u=n.contextSummary&&n.contextSummary.trim()?n.contextSummary:m||(d?$({activity:d,activeSceneId:h,selectedBlockId:b}):a?$(a):ft({contextSummary:n.contextSummary,activityContext:c})),l=await o.run({message:n.message,contextSummary:u,chatHistory:$e(n.chatHistory)}),g=l.actions,v=l.status,R=l.response.text,oe,se=lt(t)?Z(n.plan)??ee(n.message):void 0;if(se&&l.status==="ok"){let I=$e(n.chatHistory),w=re=>{if(!d)return u;let H=JSON.parse(JSON.stringify(d));for(let P of re)try{j(H,P.action,P.data)}catch{}return $({activity:H,activeSceneId:h,selectedBlockId:b})},T=await Pe({plan:se,initialActions:l.actions,maxContinuations:2,runContinuation:async(re,H)=>{let P=await o.run({message:`[SISTEMA] Continua a costruire il piano. Elementi ANCORA MANCANTI: ${z(re)}. Aggiungi SOLO questi elementi (non ricreare quelli gi\xE0 presenti), poi rispondi.`,contextSummary:w(H),chatHistory:I});return{actions:P.actions,status:P.status}}});g=T.actions,oe={expectedBlocks:se.blocks.length,missingBlocks:T.diff.missingBlocks.length,missingScenes:T.diff.missingScenes,continuations:T.continuations,complete:T.complete},T.complete||(v="partial",R=`${R} Mancano ancora: ${z(T.diff)}. Dimmi "continua" per aggiungerli.`)}let ie;if(v==="ok"){let I=ke(R,g);if(I.length>0){v="partial";let w=I.map(T=>T.label).join(", ");R=`${R} \u26A0\uFE0F Verifica: la risposta menziona ${w}, ma tra le azioni applicate non risulta l'operazione corrispondente. Il messaggio potrebbe essere impreciso \u2014 riprova se necessario.`,ie={unbackedClaims:I.map(T=>T.id),demoted:!0}}}a&&ye(r,g);let J;if(d){let I=JSON.parse(JSON.stringify(d));for(let w of g)try{j(I,w.action,w.data)}catch{}J=I}else J=a?.activity;let Ae=v==="needs_input",Ce={agent:t.name,response:{...l.response,text:R},actions:g,toolTurns:l.toolTurns,status:v,diagnostics:l.diagnostics,...oe?{planVerification:oe}:{},...ie?{fidelityCheck:ie}:{},needsInput:Ae,choices:Ae?l.response.choices??[]:[],tokens:l.diagnostics?.budget?.tokens??{prompt:0,completion:0,total:0},...J?{activity:J}:{}};return{content:[{type:"text",text:JSON.stringify(Ce)}],structuredContent:Ce,...v==="failed"?{isError:!0}:{}}})}var ne=require("zod");function mt(e){if(e&&typeof e=="object"&&!Array.isArray(e))return e;if(typeof e=="string"&&e.trim())try{let t=JSON.parse(e);return t&&typeof t=="object"&&!Array.isArray(t)?t:void 0}catch{return}}function xe(e){e.registerTool("set_activity",{title:"Set activity (session)",description:"Seed the per-session activity state for this turn. Call this ONCE at the start of each turn with the current activity so the specialists share fresh state. Returns how many scenes were stored.",inputSchema:{activity:ne.z.string().describe("The current activity object as a JSON STRING ({ title, scenes: [...], variables? }). Passed as a string (not an object) for Dify Tool-node compatibility; the server parses it. In Dify map to the `activity_context` Start input. The frontend is authoritative; pass its latest state."),activeSceneId:ne.z.string().optional().describe("The currently active scene id."),selectedBlockId:ne.z.string().optional().describe("The currently selected block id.")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},async(t,s)=>{let o=s?.sessionId;if(!o)return{content:[{type:"text",text:JSON.stringify({ok:!1,reason:"no session id (stateless connection)"})}],structuredContent:{ok:!1}};let n=mt(t.activity);if(!n)return{content:[{type:"text",text:JSON.stringify({ok:!1,reason:"activity must be a non-empty JSON object string"})}],structuredContent:{ok:!1}};let i=me(o,n,t.activeSceneId,t.selectedBlockId),r={ok:!0,scenes:Array.isArray(i.activity.scenes)?i.activity.scenes.length:0};return{content:[{type:"text",text:JSON.stringify(r)}],structuredContent:r}}),e.registerTool("get_activity",{title:"Get activity (session)",description:"Return the current per-session activity (after the specialists applied their actions this turn).",inputSchema:{},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},async(t,s)=>{let n={activity:D(s?.sessionId)?.activity??null};return{content:[{type:"text",text:JSON.stringify(n)}],structuredContent:n}}),e.registerTool("clear_activity",{title:"Clear activity (session)",description:"Drop the per-session activity state. Optional; state is also overwritten by the next set_activity.",inputSchema:{},annotations:{readOnlyHint:!1,destructiveHint:!0,idempotentHint:!0,openWorldHint:!1}},async(t,s)=>(ge(s?.sessionId),{content:[{type:"text",text:JSON.stringify({ok:!0})}],structuredContent:{ok:!0}}))}var Je=require("@edugate/authoring/canvas");function Se(e){for(let t of Je.mediaTools)e.registerTool(t.name,{title:t.name,description:t.description,inputSchema:t.inputShape,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},async s=>{let n=(await t.handler(s)).content[0]?.text??"null",i;try{let r=JSON.parse(n);i=r&&typeof r=="object"&&!Array.isArray(r)?r:void 0}catch{i=void 0}return{content:[{type:"text",text:n}],...i?{structuredContent:i}:{}}})}function gt(e){let t=new He.McpServer({name:ue,version:de});xe(t),Se(t);let s=e.agents??Ue.ALL_AGENTS;for(let o of s)Te(t,o,{llm:e.llm,maxToolTurns:e.maxToolTurns,budget:e.budget});return t}A(S,require("@edugate/authoring/agents"),module.exports);0&&(module.exports={AGENT_RESPONSE_JSON_SCHEMA,AgentExecutor,AgentResponseSchema,CLAIM_TO_OP,DEFAULT_BUDGET,DEFAULT_LLM_TIMEOUT_MS,DEFAULT_MAX_LLM_CALLS,DEFAULT_MAX_RETRIES,DEFAULT_MAX_TOOL_TURNS,DEFAULT_WALL_CLOCK_MS,LlmError,MCP_ENDPOINT_PATH,MCP_PROTOCOL_VERSION,OpenAICompatibleAdapter,RunBudget,SERVER_NAME,SERVER_VERSION,actionBlockKey,applyMutation,applySessionActions,buildContextString,callWithRetry,classifyStatus,classifyThrown,clearSession,createServer,derivePlanFromText,diffPlan,findUnbackedClaims,getSession,isComplete,parsePlan,parseRetryAfter,planBlockKey,registerMediaTools,registerSessionTools,registerSpecialistTool,setSessionActivity,specialistToolName,summarizeMissing,...require("@edugate/authoring/agents")});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
var Ee=new Set(["rate_limit","server","timeout","network"]),T=class extends Error{constructor(s,o,n,i,r){super(o);this.kind=s;this.status=n;this.retryAfterMs=i;this.cause=r;this.name="LlmError"}kind;status;retryAfterMs;cause;get retryable(){return Ee.has(this.kind)}};function q(e){return e===429?"rate_limit":e>=500?"server":e===401||e===403?"auth":e===400||e===404||e===413||e===422?"bad_request":"unknown"}function C(e){if(e instanceof T)return e;let t=e?.name,s=String(e?.message??e);return t==="AbortError"||/\baborted\b|timed? ?out|timeout/i.test(s)?new T("timeout",s,void 0,void 0,e):/fetch failed|ECONNRESET|ENOTFOUND|ECONNREFUSED|EAI_AGAIN|socket hang up|network|ETIMEDOUT/i.test(s)?new T("network",s,void 0,void 0,e):new T("unknown",s,void 0,void 0,e)}function W(e,t=Date.now){if(!e)return;let s=String(e).trim();if(s==="")return;let o=Number(s);if(Number.isFinite(o))return Math.max(0,Math.round(o*1e3));let n=Date.parse(s);if(!Number.isNaN(n))return Math.max(0,n-t())}var V=class{constructor(t){this.cfg=t}cfg;async executeTurn(t,s,o,n,i){let r=[{role:"system",content:t},...s.map(De)],a={model:this.cfg.model,messages:r,temperature:i?.temperature??this.cfg.temperature??.2};this.cfg.fallbackModels&&this.cfg.fallbackModels.length>0&&(a.models=[this.cfg.model,...this.cfg.fallbackModels]),i?.seed!==void 0&&(a.seed=i.seed),o&&o.length>0?(a.tools=o.map(b=>({type:"function",function:{name:b.name,description:b.description,parameters:b.parameters}})),a.tool_choice="auto"):n?a.response_format={type:"json_schema",json_schema:{name:"agent_response",strict:!0,schema:n}}:a.response_format={type:"json_object"};let c=await this.post(a,i),f=c?.choices?.[0]?.message??{},m=Ne(c?.usage),d=f.tool_calls;if(d&&d.length>0){let b=d.map((l,g)=>({id:l.id||`call_${g}`,type:"function",function:{name:l.function.name,arguments:l.function.arguments||"{}"}})),u=le(f.content);return{toolCalls:b,response:u??void 0,usage:m}}return{response:le(f.content)??{text:typeof f.content=="string"?f.content:""},usage:m}}async post(t,s){let o=`${this.cfg.baseUrl.replace(/\/$/,"")}/chat/completions`,n=new AbortController,i=s?.timeoutMs??this.cfg.timeoutMs??12e4,r=setTimeout(()=>n.abort(),i),a=()=>n.abort();s?.signal&&(s.signal.aborted?n.abort():s.signal.addEventListener("abort",a,{once:!0}));try{let c=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",...this.cfg.apiKey?{Authorization:`Bearer ${this.cfg.apiKey}`}:{}},body:JSON.stringify(t),signal:n.signal});if(!c.ok){let p=await c.text().catch(()=>"");throw new T(q(c.status),`LLM request failed (${c.status} ${c.statusText}): ${p.slice(0,500)}`,c.status,W(c.headers.get("retry-after")))}return await c.json()}catch(c){throw C(c)}finally{clearTimeout(r),s?.signal&&s.signal.removeEventListener("abort",a)}}};function Ne(e){if(!e||typeof e!="object")return;let t=e,s=r=>typeof r=="number"&&Number.isFinite(r)?r:0,o=s(t.prompt_tokens),n=s(t.completion_tokens),i=s(t.total_tokens)||o+n;if(!(!i&&!o&&!n))return{promptTokens:o,completionTokens:n,totalTokens:i}}function De(e){let t={role:e.role,content:e.content};return e.tool_call_id&&(t.tool_call_id=e.tool_call_id),e.tool_calls&&(t.tool_calls=e.tool_calls),t}function le(e){if(typeof e!="string"||e.trim()==="")return null;let t=e.trim();try{return JSON.parse(t)}catch{let s=t.indexOf("{"),o=t.lastIndexOf("}");if(s!==-1&&o>s)try{return JSON.parse(t.slice(s,o+1))}catch{}return null}}import{z as h}from"zod";var G=h.object({text:h.string().describe("A SHORT introductory or confirmation text (max 2 sentences). DO NOT include bullet points, DO NOT include links, DO NOT include options here. If there are options or search results, you MUST use the 'choices' field."),actionMeta:h.object({type:h.string().describe("The kind of operation performed, e.g. addBlock, updateBlockTheme, configureTimer, manageVariables, none."),targetId:h.string().nullable().optional().describe("The ID of the scene or block you are operating on. Omit for activity-level operations (e.g. global variables or activity timer).")}).optional().describe("Optional. IMPORTANT: You must ALWAYS call the tools! You cannot execute actions just by returning this JSON."),choices:h.array(h.object({question:h.string().describe("The question regarding the doubt to be resolved or the options proposed to the user."),elementId:h.string().describe("The ID of the block or scene to which this specific choice refers.").optional().nullable(),options:h.array(h.string()).describe("List of fixed options (2 to 4) to choose from. DO NOT include 'Other' here.")})).optional().describe("Guided multiple choice groups. The frontend will show these options + a fixed text input for the 'Other' option."),revertSnapshot:h.any().optional().describe("Optional. The previous state of the activity."),delegateTo:h.string().nullable().optional().describe("Used ONLY by the general assistant. When the user's request actually requires a specialist (structural, content, styling, action), set this to that agent's id so the orchestrator re-routes. Leave null/omit for pure conversation.")}),Y={type:"object",additionalProperties:!1,required:["text"],properties:{text:{type:"string"},actionMeta:{type:"object",additionalProperties:!1,required:["type"],properties:{type:{type:"string"},targetId:{type:["string","null"]}}},choices:{type:"array",items:{type:"object",additionalProperties:!1,required:["question","options"],properties:{question:{type:"string"},elementId:{type:["string","null"]},options:{type:"array",items:{type:"string"}}}}},delegateTo:{type:["string","null"]}}};import{resolveTools as Le}from"@edugate/authoring/canvas";import{toolParametersSchema as Pe,validateToolInput as je}from"@edugate/authoring/canvas";var X={maxToolTurns:8,maxLlmCalls:24,maxRetriesPerCall:2,wallClockMs:24e4,minCallTimeoutMs:15e3,maxCallTimeoutMs:12e4,adaptive:!0};function ue(e){let t=e??"",s=[...t.matchAll(/\b(\d{1,2})\b/g)].map(i=>Number(i[1])).filter(i=>i>=2&&i<=30),o=s.length?Math.max(...s):0,n=(t.match(/[,;]|\b(?:e|and)\b|\n\s*[-*\d]/gi)??[]).length;return Math.max(1,o,Math.ceil(n/2))}function de(e,t){if(t<=1)return e;let o=t*2+2,n=Math.min(e.maxToolTurns*4,Math.max(e.maxToolTurns,o)),i=Math.min(e.maxLlmCalls*4,Math.max(e.maxLlmCalls,n*2+4));return{...e,maxToolTurns:n,maxLlmCalls:i}}var M=class{constructor(t,s=Date.now){this.config=t;this.now=s;this.startedAt=s()}config;now;startedAt;llmCalls=0;retries=0;toolTurns=0;promptTokens=0;completionTokens=0;totalTokens=0;extraToolTurns=0;errorsByKind={};elapsedMs(){return this.now()-this.startedAt}remainingMs(){return Math.max(0,this.config.wallClockMs-this.elapsedMs())}remainingCalls(){return Math.max(0,this.config.maxLlmCalls-this.llmCalls)}toolTurnCeiling(){return Math.min(this.config.maxToolTurns+this.extraToolTurns,this.config.maxToolTurns*2)}canTakeToolTurn(){return this.toolTurns<this.toolTurnCeiling()}grantExtraToolTurns(t){return this.toolTurns>=this.config.maxToolTurns*2?!1:(this.extraToolTurns+=t,!0)}canStartCall(){return this.remainingMs()<=0?{ok:!1,reason:"wall_clock"}:this.remainingCalls()<=0?{ok:!1,reason:"llm_calls"}:{ok:!0,reason:null}}perCallTimeoutMs(){return Math.max(this.config.minCallTimeoutMs,Math.min(this.config.maxCallTimeoutMs,this.remainingMs()))}spendCall(){this.llmCalls++}spendTokens(t){t&&(this.promptTokens+=t.promptTokens??0,this.completionTokens+=t.completionTokens??0,this.totalTokens+=t.totalTokens??(t.promptTokens??0)+(t.completionTokens??0))}spendRetry(){this.retries++}recordError(t){this.errorsByKind[t]=(this.errorsByKind[t]??0)+1}snapshot(){return{elapsedMs:this.elapsedMs(),llmCalls:this.llmCalls,retries:this.retries,toolTurns:this.toolTurns,tokens:{prompt:this.promptTokens,completion:this.completionTokens,total:this.totalTokens},errorsByKind:{...this.errorsByKind},config:this.config}}};var Be=e=>new Promise(t=>setTimeout(t,e));async function Q(e,t,s={}){let o=s.sleep??Be,n=s.jitter??Math.random,i=s.baseDelayMs??500,r=s.maxDelayMs??8e3,a=0,c;for(;;){let p=t.canStartCall();if(!p.ok)throw c??new T("timeout",`request budget exhausted (${p.reason}) before a successful LLM call`);t.spendCall();try{return await e(t.perCallTimeoutMs())}catch(f){let m=C(f);if(t.recordError(m.kind),c=m,!m.retryable||a>=t.config.maxRetriesPerCall)throw m;let d=Math.min(r,i*2**a),y=m.retryAfterMs??Math.floor(d*n());if(t.remainingMs()-y<t.config.minCallTimeoutMs)throw m;t.spendRetry(),a++,y>0&&await o(y)}}}var pe=Y,$e=3,ze="Non hai ancora completato tutti gli elementi richiesti. Continua con quelli mancanti chiamando i tool necessari (una chiamata diversa per ogni elemento). Restituisci il JSON finale SOLO quando l'intera richiesta \xE8 stata soddisfatta.",Je=new Set(["x","y"]);function Z(e){return Array.isArray(e)?e.map(Z):e&&typeof e=="object"?Object.fromEntries(Object.entries(e).filter(([t])=>!Je.has(t)).sort(([t],[s])=>t.localeCompare(s)).map(([t,s])=>[t,Z(s)])):e}function fe(e,t){try{return`${e}:${JSON.stringify(Z(JSON.parse(t)))}`}catch{return`${e}:${t}`}}function R(e,t,s){return JSON.stringify({status:"error",code:e,message:t,...s?{errors:s}:{}})}function He(e){let t=e.map(o=>o.message).filter(o=>!!o);if(e.length===0)return"Nessuna modifica applicata.";if(e.length===1)return t[0]??"Modifica applicata.";let s=t.slice(0,4).join(" ");return`Ho applicato ${e.length} modifiche. ${s}`.trim()}function Ue(e,t){if(e)switch(e.kind){case"rate_limit":return"Il servizio AI \xE8 momentaneamente sovraccarico (rate limit). Riprova tra qualche secondo.";case"timeout":return"Il servizio AI non ha risposto in tempo. Riprova; se usi un modello on-prem lento, aumenta il timeout.";case"server":case"network":return"Il servizio AI non \xE8 raggiungibile in questo momento. Riprova tra poco.";case"auth":return"Configurazione del servizio AI non valida (autenticazione). Contatta un amministratore.";case"bad_request":return"La richiesta al modello non \xE8 valida per questo provider. Potrebbe non supportare i tool o lo schema richiesto.";default:return"Si \xE8 verificato un errore con il servizio AI. Riprova."}return t==="wall_clock"||t==="llm_calls"?"Non sono riuscito a completare la richiesta entro il budget disponibile. Prova a semplificare o a ripetere la richiesta.":"Non sono riuscito a eseguire la richiesta: il modello non ha prodotto un'azione valida. Riprova o riformula la richiesta."}var E=class{constructor(t,s,o={}){this.agent=t;this.llm=s;this.tools=Le(t.toolNames),this.toolDefs=this.tools.map(n=>({name:n.name,description:n.description,parameters:Pe(n.inputShape)})),this.allowed=new Set(t.toolNames),this.budgetCfg={...X,...o.maxToolTurns!==void 0?{maxToolTurns:o.maxToolTurns}:{},...o.budget??{}},this.retryDeps=o.retry??{},this.now=o.now}agent;llm;tools;toolDefs;allowed;budgetCfg;retryDeps;now;async run(t){let s=this.budgetCfg.adaptive===!1?this.budgetCfg:de(this.budgetCfg,ue(t.message)),o=new M(s,this.now),n=new Map(this.tools.map(u=>[u.name,u])),i=this.toolDefs.length>0,r=t.contextSummary?`${t.message}
|
|
2
|
+
${t.contextSummary}`:t.message,a=[...t.chatHistory??[],{role:"user",content:r}],c=[],p=new Set,f,m=async(u,l)=>{let g=await Q(S=>this.llm.executeTurn(this.agent.systemPrompt,a,u,l,{timeoutMs:S}),o,this.retryDeps);return o.spendTokens(g.usage),g},d=async()=>{for(;o.canTakeToolTurn()&&o.canStartCall().ok;){let u;try{u=await m(i?this.toolDefs:void 0,i?void 0:pe)}catch(l){return f=C(l),null}if(u.toolCalls&&u.toolCalls.length>0){o.toolTurns++,a.push({role:"assistant",content:u.response?.text??"",tool_calls:u.toolCalls});let l=!0;for(let g of u.toolCalls)if(!p.has(fe(g.function.name,g.function.arguments))){l=!1;break}for(let g of u.toolCalls)await this.executeToolCall(g,n,a,c,p);if(l)return null;continue}if(u.response){let l=this.finalize(u.response);if(l)return this.settleFinal(l,c,o)}return null}return null},y=await d();if(y)return y;let b=0;for(;s.adaptive!==!1&&!f&&c.length>0&&b<$e&&!o.canTakeToolTurn()&&o.canStartCall().ok&&o.grantExtraToolTurns(2);)if(b++,a.push({role:"user",content:ze}),y=await d(),y)return y;if(!f&&o.canStartCall().ok){a.push({role:"user",content:"You have reached the maximum number of tool calls. Provide your final response now as JSON."});try{let u=await m(void 0,pe),l=u.response?this.finalize(u.response):null;if(l)return this.settleFinal(l,c,o)}catch(u){f=C(u)}}return this.terminate(c,o,f)}settleFinal(t,s,o){if(!(Array.isArray(t.choices)&&t.choices.length>0))return this.ok(t,s,o);let i={status:"needs_input",termination:"clarification",budget:o.snapshot()};return{response:t,actions:s,toolTurns:o.toolTurns,status:"needs_input",diagnostics:i}}ok(t,s,o){let n={status:"ok",termination:"final_response",budget:o.snapshot()};return{response:t,actions:s,toolTurns:o.toolTurns,status:"ok",diagnostics:n}}terminate(t,s,o){let n=s.canStartCall(),i=n.ok?null:n.reason;if(t.length>0){let a={status:"partial",termination:o?"llm_error":i?"budget_exhausted":"partial_actions",exhausted:i??void 0,lastErrorKind:o?.kind,budget:s.snapshot()};return{response:{text:He(t)},actions:t,toolTurns:s.toolTurns,status:"partial",diagnostics:a}}let r={status:"failed",termination:o?"llm_error":i?"budget_exhausted":"no_output",exhausted:i??void 0,lastErrorKind:o?.kind,budget:s.snapshot()};return{response:{text:Ue(o,i)},actions:t,toolTurns:s.toolTurns,status:"failed",diagnostics:r}}async executeToolCall(t,s,o,n,i){let r=fe(t.function.name,t.function.arguments);if(i.has(r)){o.push({role:"tool",tool_call_id:t.id,content:R("DUPLICATE","Duplicate call blocked. Provide your final response now.")});return}if(i.add(r),!this.allowed.has(t.function.name)){o.push({role:"tool",tool_call_id:t.id,content:R("NOT_ALLOWED",`Tool "${t.function.name}" is not available for this agent.`)});return}let a=s.get(t.function.name);if(!a){o.push({role:"tool",tool_call_id:t.id,content:R("UNKNOWN_TOOL",`Tool "${t.function.name}" does not exist.`)});return}let c;try{c=JSON.parse(t.function.arguments||"{}")}catch{o.push({role:"tool",tool_call_id:t.id,content:R("BAD_JSON",`Arguments for "${t.function.name}" were not valid JSON.`)});return}let p=je(a.inputShape,c);if(!p.ok){o.push({role:"tool",tool_call_id:t.id,content:R("INVALID_ARGS",`Invalid arguments for "${t.function.name}". Fix them and re-emit the call.`,p.errors)});return}try{let m=(await a.handler(p.value)).content[0]?.text??"{}",d=Ke(m);d?.status==="success"&&typeof d.action=="string"?(n.push({action:d.action,data:d.data,message:d.message??""}),o.push({role:"tool",tool_call_id:t.id,content:this.agent.name==="content"?m:Fe(d)})):o.push({role:"tool",tool_call_id:t.id,content:m})}catch(f){o.push({role:"tool",tool_call_id:t.id,content:R("TOOL_ERROR",f?.message??String(f))})}}finalize(t){let s=G.safeParse(t);if(s.success)return s.data;let o=typeof t?.text=="string"?t.text.trim():"";return o.length>0?{text:o}:null}};function Ke(e){try{return JSON.parse(e)}catch{return null}}function Fe(e){let t=e&&e.data||{},s=t?.block?.id??t?.id??t?.blockId??t?.sceneId??void 0,o={status:"success",action:e.action};return s!==void 0&&(o.id=s),t?.sceneId&&t.sceneId!==s&&(o.sceneId=t.sceneId),e.message&&(o.message=e.message),JSON.stringify(o)}import{McpServer as ot}from"@modelcontextprotocol/sdk/server/mcp.js";import{ALL_AGENTS as st}from"@edugate/authoring/agents";var me="edugate-agents",ge="1.0.0",Bt="2025-03-26",Lt="/mcp",Pt=8,jt=24,$t=2,zt=24e4,Jt=12e4;import{z as B}from"zod";var te=new Map;function he(e){return e==null?e:JSON.parse(JSON.stringify(e))}function be(e,t,s,o){let n={activity:he(t)??{scenes:[]},activeSceneId:s,selectedBlockId:o};return Array.isArray(n.activity.scenes)||(n.activity.scenes=[]),te.set(e,n),n}function N(e){if(e)return te.get(e)}function ke(e){e&&te.delete(e)}function Te(e,t){let s=N(e);if(s)for(let o of t)try{j(s.activity,o.action,o.data)}catch{}}function O(e,t){return(e.scenes||[]).find(s=>s.id===t)}function x(e,t){for(let s of e.scenes||[]){let o=(s.blocks||[]).find(n=>n.id===t);if(o)return o}}function j(e,t,s){if(!e||typeof e!="object")return;Array.isArray(e.scenes)||(e.scenes=[]);let o=e.scenes,n=s||{};switch(t){case"createScene":{let{newIndex:i,...r}=n;r.blocks||(r.blocks=[]);let a=typeof i=="number"?ee(i,o.length):o.length;o.splice(a,0,r);break}case"updateScene":{let i=O(e,n.sceneId);if(!i)break;n.updates&&typeof n.updates=="object"&&Object.assign(i,n.updates),typeof n.newIndex=="number"&&ye(o,i,n.newIndex);break}case"deleteScene":{let i=o.findIndex(r=>r.id===n.sceneId);i>=0&&o.splice(i,1);break}case"reorderScenes":{let i=O(e,n.sceneId);i&&typeof n.newIndex=="number"&&ye(o,i,n.newIndex);break}case"addBlock":{let i=O(e,n.sceneId);if(!i||!n.block)break;Array.isArray(i.blocks)||(i.blocks=[]),i.blocks.push(n.block);break}case"updateBlock":{let i=n.sceneId?w(e,n.sceneId,n.blockId):x(e,n.blockId);i&&n.updates&&typeof n.updates=="object"&&qe(i,n.updates);break}case"updateBlockTheme":{let i=w(e,n.sceneId,n.blockId)||x(e,n.blockId);i&&n.theme&&(i.theme=xe(i.theme||{},n.theme));break}case"updateBlockElementStyle":{let i=w(e,n.sceneId,n.blockId)||x(e,n.blockId);i&&(i.elementStyles=i.elementStyles||{},n.elementId&&(i.elementStyles[n.elementId]={...i.elementStyles[n.elementId]||{},...n.customStyle||{}}));break}case"deleteBlock":{let i=O(e,n.sceneId);if(!i||!Array.isArray(i.blocks))break;let r=i.blocks.findIndex(a=>a.id===n.blockId);r>=0&&i.blocks.splice(r,1);break}case"duplicateBlock":{let i=O(e,n.sceneId);if(!i||!Array.isArray(i.blocks))break;let r=i.blocks.find(a=>a.id===n.blockId);if(r){let a=he(r);a.id=`${r.id}-copy-${i.blocks.length}`,i.blocks.push(a)}break}case"groupBlocks":{let i=Array.isArray(n.blockIds)?n.blockIds:[],r=`group-${i.join("-").slice(0,24)}`;for(let a of i){let c=x(e,a);c&&(c.groupId=r)}break}case"addAction":{let i=w(e,n.sceneId,n.blockId)||x(e,n.blockId);i&&n.action&&(i.actions=Array.isArray(i.actions)?i.actions:[],i.actions.push(n.action));break}case"removeAction":{let i=w(e,n.sceneId,n.blockId)||x(e,n.blockId);i&&Array.isArray(i.actions)&&(i.actions=i.actions.filter(r=>r&&r.id!==n.actionId));break}case"manageVariables":{e.variables=Array.isArray(e.variables)?e.variables:[];let i=n.operation;if(i==="add"||i==="update"){let r=e.variables.find(a=>a.name===n.name);r?r.defaultValue=n.defaultValue:e.variables.push({name:n.name,defaultValue:n.defaultValue})}else i==="remove"&&(e.variables=e.variables.filter(r=>r.name!==n.name));break}case"configureTimer":{if(n.scope==="block"){let i=w(e,n.sceneId,n.blockId)||x(e,n.blockId);i&&(i.hasTimer=n.hasTimer??!0,i.timer=n.timer)}else e.hasTimer=n.hasTimer??!0,e.timer=n.timer;break}case"insertItem":{let i=x(e,n.blockId);if(i&&n.collection&&n.item){i[n.collection]=Array.isArray(i[n.collection])?i[n.collection]:[];let r=typeof n.at=="number"?ee(n.at,i[n.collection].length):i[n.collection].length;i[n.collection].splice(r,0,n.item)}break}case"updateItem":{let i=x(e,n.blockId);i&&Array.isArray(i[n.collection])&&(i[n.collection]=i[n.collection].map(r=>r&&typeof r=="object"&&r.id===n.itemId?{...r,...n.item||{}}:r));break}case"deleteItem":{let i=x(e,n.blockId);i&&Array.isArray(i[n.collection])&&(i[n.collection]=i[n.collection].filter(r=>!(r&&typeof r=="object"&&r.id===n.itemId)));break}default:break}}function w(e,t,s){let o=O(e,t);if(!(!o||!Array.isArray(o.blocks)))return o.blocks.find(n=>n.id===s)}function qe(e,t){for(let[s,o]of Object.entries(t))o&&typeof o=="object"&&!Array.isArray(o)&&e[s]&&typeof e[s]=="object"?e[s]={...e[s],...o}:e[s]=o}function xe(e,t){let s={...e||{}};for(let[o,n]of Object.entries(t||{}))s[o]=n&&typeof n=="object"&&!Array.isArray(n)?xe(s[o]||{},n):n;return s}function ee(e,t){return Math.max(0,Math.min(e,t))}function ye(e,t,s){let o=e.indexOf(t);o<0||(e.splice(o,1),e.splice(ee(s,e.length),0,t))}function $(e){let t=e.activity||{},s=t.scenes||[],o=(a,c)=>{let p=a.position?`, Position: {x: ${Math.round(a.position.x)}, y: ${Math.round(a.position.y)}} (0-100%)`:"",f=a.size?`, Size: {width: ${a.size.width}, height: ${a.size.height}} (px)`:"",m=a.id===e.selectedBlockId?" \u2605SELECTED":"";return`[Block ${c+1}: ID: ${a.id}, Type: ${a.type}, Title: "${a.title}"${p}${f}${m}]`},n=(a,c)=>{let p=a.blocks&&a.blocks.length?a.blocks.map(o).join(", "):"No blocks";return`- Scene ${c+1}: Title "${a.title}", ID: "${a.id}"
|
|
3
|
+
Blocks: ${p}`},i=e.selectedBlockId?x(t,e.selectedBlockId):void 0,r=i?`
|
|
4
|
+
Selected block content (ID: ${i.id}, Type: ${i.type}) \u2014 when reconfiguring, PRESERVE these items and add to them:
|
|
5
|
+
${JSON.stringify(i)}`:"";return`
|
|
6
|
+
[CONTEXT] Activity: "${t.title||"Untitled"}"
|
|
7
|
+
Scenes list (numbered 1-based):
|
|
8
|
+
${s.length?s.map(n).join(`
|
|
9
|
+
`):"None"}
|
|
10
|
+
Active scene ID: ${e.activeSceneId||"none"}.`+r+`
|
|
11
|
+
Block positions are already percentages (0-100) of the scene canvas; no separate canvas dimensions are provided \u2014 use them as-is.
|
|
12
|
+
When the user references a scene/block by number, resolve it to its ID from this list. If the user does NOT specify a scene, operate on the Active scene ID. Every scene/block listed above EXISTS \u2014 act on it by its ID; never ask the user for an ID that is present here.`}import{PREFABS_BY_ID as We}from"@edugate/authoring/prefabs";import{ALL_BLOCK_TYPES as Se}from"@edugate/authoring/canvas";function Ce(e,t){let s=e??(t?We[t]?.blockType:void 0);return s?String(s).trim().toLowerCase():void 0}function D(e){return Ce(e.blockType,e.prefabId)}function ve(e){if(e.action==="addBlock")return Ce(e.data?.block?.type,e.data?.prefabId)}function se(e){let t=e;if(typeof e=="string"){if(!e.trim())return;try{t=JSON.parse(e)}catch{return}}if(!t||typeof t!="object")return;let s=[],o;if(Array.isArray(t.blocks))s=t.blocks.filter(n=>n&&typeof n=="object");else if(Array.isArray(t.scenes)){o=[];for(let n of t.scenes)if(!(!n||typeof n!="object")&&(o.push({sceneId:n.sceneId,title:n.title}),Array.isArray(n.blocks)))for(let i of n.blocks)i&&typeof i=="object"&&s.push({...i,scene:i.scene??n.sceneId})}if(Array.isArray(t.scenes)&&!o&&(o=t.scenes.filter(n=>n&&typeof n=="object").map(n=>({sceneId:n.sceneId,title:n.title}))),s=s.filter(n=>D(n)!==void 0),!(s.length===0&&(!o||o.length===0)))return{blocks:s,scenes:o}}var Ae={grafico:"Chart",immagine:"Media",immagini:"Media",foto:"Media",sondaggio:"Quiz",cruciverba:"Crossword",memoria:"Memory",ruota:"SpinningWheel",dado:"Dice"};function ie(e){if(typeof e!="string"||!e.trim())return;let t=[...Se,...Object.keys(Ae)],s=new RegExp("\\b("+t.join("|")+")\\b","gi"),o=[],n;for(;(n=s.exec(e))!==null;){let i=n[1].toLowerCase(),r=Se.find(a=>a.toLowerCase()===i)??Ae[i];r&&o.push({blockType:r})}return o.length>=2?{blocks:o}:void 0}function ne(e,t){let s=new Map;for(let a of t){let c=ve(a);c&&s.set(c,(s.get(c)??0)+1)}let o=[];for(let a of e.blocks){let c=D(a);if(!c)continue;let p=s.get(c)??0;p>0?s.set(c,p-1):o.push(a)}let n=e.scenes?.length??0,i=t.filter(a=>a.action==="createScene").length,r=Math.max(0,n-i);return{missingBlocks:o,missingScenes:r}}function oe(e){return e.missingBlocks.length===0&&e.missingScenes===0}function z(e){let t=[];e.missingScenes>0&&t.push(`${e.missingScenes} scena/e ancora da creare`);for(let s of e.missingBlocks)t.push(s.label?`${s.label} (${D(s)})`:D(s));return t.join(", ")}async function Ie(e){let t=e.maxContinuations??2,s=[...e.initialActions],o=ne(e.plan,s),n=0;for(;!oe(o)&&n<t;){let i=o.missingBlocks.length+o.missingScenes,r=await e.runContinuation(o,s);n++,r.actions.length&&s.push(...r.actions),o=ne(e.plan,s);let a=o.missingBlocks.length+o.missingScenes;if(r.status!=="ok"&&r.status!=="success"||a>=i)break}return{actions:s,diff:o,continuations:n,complete:oe(o)}}var J=(e,...t)=>e.some(s=>t.includes(s.action)),Ve=[{id:"timer",label:"il timer",detect:[/\b(imposta|impost|configur|aggiun|attiv|cre|sett|mett|mess|abilit)[a-z]*\s+(?:un[oa]?\s+|il\s+|lo\s+|la\s+|del\s+)?timer\b/i,/\btimer\b[^.!?\n]{0,25}?\b\d+\s*(second|minut|sec\b|min\b)/i,/\btimer\b\s*(?:di|da|a|of|for|to)\s*\d+/i,/\b(set|add|configur|enabl|creat)[a-z]*\s+(?:a\s+|the\s+)?timer\b/i],satisfied:e=>J(e,"configureTimer")},{id:"variable",label:"la variabile",detect:[/\b(cre|aggiun|impost|impost|defin|configur|introdott|settat)[a-z]*\s+(?:una\s+|la\s+|il\s+|un[oa]?\s+|nuov[ao]\s+)*variabil[ei]\b/i,/\b(creat|add|set|defin|configur|introduc)[a-z]*\s+(?:a\s+|the\s+|new\s+)*variable[s]?\b/i],satisfied:e=>J(e,"manageVariables")||e.some(t=>t.action==="addAction"&&typeof t.data=="object"&&t.data!==null&&["set_variable","math_variable"].includes(t.data.action?.type??""))},{id:"score",label:"il punteggio",detect:[/\b(imposta|impost|assegn|configur|aggiun|sett|attribu|dat[oa]|val[a-z]*)[a-z]*\s+(?:un\s+|il\s+|lo\s+|di\s+)?punteggi[oa]\b/i,/\bpunteggi[oa]\b[^.!?\n]{0,20}?\b\d+\b/i,/\b(set|assign|configur|add|award|give[ns]?)[a-z]*\s+(?:a\s+|the\s+)?score\b/i],satisfied:e=>e.some(t=>t.action==="updateBlock"&&typeof t.data=="object"&&t.data!==null&&Object.prototype.hasOwnProperty.call(t.data.updates??{},"score"))||J(e,"addBlock")},{id:"background",label:"lo sfondo",detect:[/\b(cambi|imposta|impost|modific|aggiorn|sett|mett|mess|applicat|colorat)[a-z]*\s+(?:lo\s+|il\s+|un\s+|nuovo\s+)?sfondo\b/i,/\b(chang|set|updat|appl|creat)[a-z]*\s+(?:the\s+|a\s+|new\s+)?background\b/i],satisfied:e=>J(e,"updateScene","updateBlockTheme")}];function Re(e,t){if(typeof e!="string"||!e.trim())return[];let s=[];for(let o of Ve)o.detect.some(i=>i.test(e))&&!o.satisfied(t)&&s.push({id:o.id,label:o.label});return s}function Ge(e){return e.toolNames.includes("add_block")}function Ye(e){return`${e.name}_agent`}var Xe={message:B.string().describe("The user's natural-language instruction for this specialist (in any language)."),activityContext:B.string().optional().describe("The current activity state as a JSON STRING (scenes, blocks, selection, variables). Passed as a string (not an object) for Dify Tool-node compatibility; the server parses it. Optional \u2014 the per-session state seeded by set_activity is preferred when present. In Dify map to the `activity_context` Start input."),contextSummary:B.string().optional().describe("Optional pre-rendered [CONTEXT] block. If provided it is used verbatim instead of parsing activityContext."),chatHistory:B.string().optional().describe('Optional prior conversation turns as a JSON STRING array: [{"role":"user|assistant","content":"..."}]. Passed as a string (not an array) for Dify Tool-node compatibility; the server parses it.'),plan:B.string().optional().describe('Optional STRUCTURED build plan as a JSON STRING: {"scenes":[{"sceneId":"scene-1","title":"..."}],"blocks":[{"prefabId":"quiz.singlechoice","scene":"scene-1","role":"assessment","label":"..."}]}. When present, the server verifies the build against it by prefab/type IDENTITY and continues building only the specific missing items. Passed as a string (not an object) for Dify Tool-node compatibility. In Dify bind to the persisted `conv_plan` from the planner turn.')};function Qe(e){if(e&&typeof e=="object"&&!Array.isArray(e))return e;if(typeof e=="string"&&e.trim())try{let t=JSON.parse(e);return t&&typeof t=="object"&&!Array.isArray(t)?t:void 0}catch{return}}function we(e){let t=typeof e=="string"&&e.trim()?Ze(e):e;if(!Array.isArray(t))return;let s=t.filter(o=>!!o&&typeof o=="object"&&typeof o.role=="string"&&typeof o.content=="string");return s.length?s:void 0}function Ze(e){try{return JSON.parse(e)}catch{return}}function et(e){if(e.contextSummary&&e.contextSummary.trim())return e.contextSummary;if(e.activityContext)return`[CONTEXT]
|
|
13
|
+
${JSON.stringify(e.activityContext)}`}function Oe(e,t,s){let o=new E(t,s.llm,{maxToolTurns:s.maxToolTurns,budget:s.budget});e.registerTool(Ye(t),{title:t.card.name,description:t.card.description,inputSchema:Xe,annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0}},async(n,i)=>{let r=i?.sessionId,a=N(r),c=Qe(n.activityContext),p=c?.activity,f=c&&Array.isArray(c.scenes)?c:void 0,m=typeof n.activityContext=="string"&&n.activityContext.trim().startsWith("[CONTEXT]")?n.activityContext:void 0,d=p??f??a?.activity,y=(typeof c?.activeSceneId=="string"?c.activeSceneId:void 0)??a?.activeSceneId,b=(typeof c?.selectedBlockId=="string"?c.selectedBlockId:void 0)??a?.selectedBlockId,u=n.contextSummary&&n.contextSummary.trim()?n.contextSummary:m||(d?$({activity:d,activeSceneId:y,selectedBlockId:b}):a?$(a):et({contextSummary:n.contextSummary,activityContext:c})),l=await o.run({message:n.message,contextSummary:u,chatHistory:we(n.chatHistory)}),g=l.actions,S=l.status,v=l.response.text,H,U=Ge(t)?se(n.plan)??ie(n.message):void 0;if(U&&l.status==="ok"){let A=we(n.chatHistory),I=F=>{if(!d)return u;let P=JSON.parse(JSON.stringify(d));for(let _ of F)try{j(P,_.action,_.data)}catch{}return $({activity:P,activeSceneId:y,selectedBlockId:b})},k=await Ie({plan:U,initialActions:l.actions,maxContinuations:2,runContinuation:async(F,P)=>{let _=await o.run({message:`[SISTEMA] Continua a costruire il piano. Elementi ANCORA MANCANTI: ${z(F)}. Aggiungi SOLO questi elementi (non ricreare quelli gi\xE0 presenti), poi rispondi.`,contextSummary:I(P),chatHistory:A});return{actions:_.actions,status:_.status}}});g=k.actions,H={expectedBlocks:U.blocks.length,missingBlocks:k.diff.missingBlocks.length,missingScenes:k.diff.missingScenes,continuations:k.continuations,complete:k.complete},k.complete||(S="partial",v=`${v} Mancano ancora: ${z(k.diff)}. Dimmi "continua" per aggiungerli.`)}let K;if(S==="ok"){let A=Re(v,g);if(A.length>0){S="partial";let I=A.map(k=>k.label).join(", ");v=`${v} \u26A0\uFE0F Verifica: la risposta menziona ${I}, ma tra le azioni applicate non risulta l'operazione corrispondente. Il messaggio potrebbe essere impreciso \u2014 riprova se necessario.`,K={unbackedClaims:A.map(k=>k.id),demoted:!0}}}a&&Te(r,g);let L;if(d){let A=JSON.parse(JSON.stringify(d));for(let I of g)try{j(A,I.action,I.data)}catch{}L=A}else L=a?.activity;let ae=S==="needs_input",ce={agent:t.name,response:{...l.response,text:v},actions:g,toolTurns:l.toolTurns,status:S,diagnostics:l.diagnostics,...H?{planVerification:H}:{},...K?{fidelityCheck:K}:{},needsInput:ae,choices:ae?l.response.choices??[]:[],tokens:l.diagnostics?.budget?.tokens??{prompt:0,completion:0,total:0},...L?{activity:L}:{}};return{content:[{type:"text",text:JSON.stringify(ce)}],structuredContent:ce,...S==="failed"?{isError:!0}:{}}})}import{z as re}from"zod";function tt(e){if(e&&typeof e=="object"&&!Array.isArray(e))return e;if(typeof e=="string"&&e.trim())try{let t=JSON.parse(e);return t&&typeof t=="object"&&!Array.isArray(t)?t:void 0}catch{return}}function _e(e){e.registerTool("set_activity",{title:"Set activity (session)",description:"Seed the per-session activity state for this turn. Call this ONCE at the start of each turn with the current activity so the specialists share fresh state. Returns how many scenes were stored.",inputSchema:{activity:re.string().describe("The current activity object as a JSON STRING ({ title, scenes: [...], variables? }). Passed as a string (not an object) for Dify Tool-node compatibility; the server parses it. In Dify map to the `activity_context` Start input. The frontend is authoritative; pass its latest state."),activeSceneId:re.string().optional().describe("The currently active scene id."),selectedBlockId:re.string().optional().describe("The currently selected block id.")},annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},async(t,s)=>{let o=s?.sessionId;if(!o)return{content:[{type:"text",text:JSON.stringify({ok:!1,reason:"no session id (stateless connection)"})}],structuredContent:{ok:!1}};let n=tt(t.activity);if(!n)return{content:[{type:"text",text:JSON.stringify({ok:!1,reason:"activity must be a non-empty JSON object string"})}],structuredContent:{ok:!1}};let i=be(o,n,t.activeSceneId,t.selectedBlockId),r={ok:!0,scenes:Array.isArray(i.activity.scenes)?i.activity.scenes.length:0};return{content:[{type:"text",text:JSON.stringify(r)}],structuredContent:r}}),e.registerTool("get_activity",{title:"Get activity (session)",description:"Return the current per-session activity (after the specialists applied their actions this turn).",inputSchema:{},annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}},async(t,s)=>{let n={activity:N(s?.sessionId)?.activity??null};return{content:[{type:"text",text:JSON.stringify(n)}],structuredContent:n}}),e.registerTool("clear_activity",{title:"Clear activity (session)",description:"Drop the per-session activity state. Optional; state is also overwritten by the next set_activity.",inputSchema:{},annotations:{readOnlyHint:!1,destructiveHint:!0,idempotentHint:!0,openWorldHint:!1}},async(t,s)=>(ke(s?.sessionId),{content:[{type:"text",text:JSON.stringify({ok:!0})}],structuredContent:{ok:!0}}))}import{mediaTools as nt}from"@edugate/authoring/canvas";function Me(e){for(let t of nt)e.registerTool(t.name,{title:t.name,description:t.description,inputSchema:t.inputShape,annotations:{readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0}},async s=>{let n=(await t.handler(s)).content[0]?.text??"null",i;try{let r=JSON.parse(n);i=r&&typeof r=="object"&&!Array.isArray(r)?r:void 0}catch{i=void 0}return{content:[{type:"text",text:n}],...i?{structuredContent:i}:{}}})}function pn(e){let t=new ot({name:me,version:ge});_e(t),Me(t);let s=e.agents??st;for(let o of s)Oe(t,o,{llm:e.llm,maxToolTurns:e.maxToolTurns,budget:e.budget});return t}export*from"@edugate/authoring/agents";export{Y as AGENT_RESPONSE_JSON_SCHEMA,E as AgentExecutor,G as AgentResponseSchema,Ve as CLAIM_TO_OP,X as DEFAULT_BUDGET,Jt as DEFAULT_LLM_TIMEOUT_MS,jt as DEFAULT_MAX_LLM_CALLS,$t as DEFAULT_MAX_RETRIES,Pt as DEFAULT_MAX_TOOL_TURNS,zt as DEFAULT_WALL_CLOCK_MS,T as LlmError,Lt as MCP_ENDPOINT_PATH,Bt as MCP_PROTOCOL_VERSION,V as OpenAICompatibleAdapter,M as RunBudget,me as SERVER_NAME,ge as SERVER_VERSION,ve as actionBlockKey,j as applyMutation,Te as applySessionActions,$ as buildContextString,Q as callWithRetry,q as classifyStatus,C as classifyThrown,ke as clearSession,pn as createServer,ie as derivePlanFromText,ne as diffPlan,Re as findUnbackedClaims,N as getSession,oe as isComplete,se as parsePlan,W as parseRetryAfter,D as planBlockKey,Me as registerMediaTools,_e as registerSessionTools,Oe as registerSpecialistTool,be as setSessionActivity,Ye as specialistToolName,z as summarizeMissing};
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@edugate/ai-engine",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Come Edugate governa un modello: l'adattatore, il budget, i ritentativi, lo stato di sessione, il controllo di fedeltà — e l'esecutore che fa girare uno specialista.",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"module": "dist/index.mjs",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": {
|
|
14
|
+
"types": "./dist/index.d.mts",
|
|
15
|
+
"default": "./dist/index.mjs"
|
|
16
|
+
},
|
|
17
|
+
"require": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"zod": "^3.24.2",
|
|
29
|
+
"zod-to-json-schema": "^3.23.0",
|
|
30
|
+
"@edugate/authoring": "0.2.0"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@modelcontextprotocol/sdk": ">=1.21.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.21.0",
|
|
37
|
+
"@types/node": "^25.9.1",
|
|
38
|
+
"tsup": "^8.5.1",
|
|
39
|
+
"typescript": "^5.8.3",
|
|
40
|
+
"vitest": "^4.1.8",
|
|
41
|
+
"@edugate/typescript-config": "0.0.0"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsup",
|
|
45
|
+
"check-types": "tsc --noEmit -p tsconfig.json",
|
|
46
|
+
"test": "vitest run"
|
|
47
|
+
}
|
|
48
|
+
}
|