veryfront 0.1.144 → 0.1.145

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.
Files changed (71) hide show
  1. package/esm/deno.js +1 -1
  2. package/esm/src/agent/ag-ui-handler.d.ts +77 -0
  3. package/esm/src/agent/ag-ui-handler.d.ts.map +1 -0
  4. package/esm/src/agent/ag-ui-handler.js +331 -0
  5. package/esm/src/agent/index.d.ts +2 -1
  6. package/esm/src/agent/index.d.ts.map +1 -1
  7. package/esm/src/agent/index.js +2 -1
  8. package/esm/src/agent/runtime/index.d.ts +2 -0
  9. package/esm/src/agent/runtime/index.d.ts.map +1 -1
  10. package/esm/src/agent/runtime/index.js +15 -7
  11. package/esm/src/agent/runtime/resume-session.d.ts +61 -0
  12. package/esm/src/agent/runtime/resume-session.d.ts.map +1 -0
  13. package/esm/src/agent/runtime/resume-session.js +232 -0
  14. package/esm/src/agent/runtime/tool-helpers.d.ts +4 -2
  15. package/esm/src/agent/runtime/tool-helpers.d.ts.map +1 -1
  16. package/esm/src/agent/runtime/tool-helpers.js +57 -4
  17. package/esm/src/agent/types.d.ts +2 -1
  18. package/esm/src/agent/types.d.ts.map +1 -1
  19. package/esm/src/internal-agents/session-manager.d.ts +8 -24
  20. package/esm/src/internal-agents/session-manager.d.ts.map +1 -1
  21. package/esm/src/internal-agents/session-manager.js +55 -178
  22. package/esm/src/mcp/index.d.ts +2 -0
  23. package/esm/src/mcp/index.d.ts.map +1 -1
  24. package/esm/src/mcp/index.js +2 -0
  25. package/esm/src/mcp/server.d.ts +1 -0
  26. package/esm/src/mcp/server.d.ts.map +1 -1
  27. package/esm/src/mcp/server.js +47 -8
  28. package/esm/src/mcp/session.d.ts +9 -0
  29. package/esm/src/mcp/session.d.ts.map +1 -0
  30. package/esm/src/mcp/session.js +25 -0
  31. package/esm/src/mcp/sse.d.ts +8 -0
  32. package/esm/src/mcp/sse.d.ts.map +1 -0
  33. package/esm/src/mcp/sse.js +17 -0
  34. package/esm/src/platform/cloud/resolver.d.ts.map +1 -1
  35. package/esm/src/platform/cloud/resolver.js +13 -3
  36. package/esm/src/provider/index.d.ts +2 -0
  37. package/esm/src/provider/index.d.ts.map +1 -1
  38. package/esm/src/provider/index.js +1 -0
  39. package/esm/src/provider/veryfront-cloud/context.d.ts +10 -0
  40. package/esm/src/provider/veryfront-cloud/context.d.ts.map +1 -0
  41. package/esm/src/provider/veryfront-cloud/context.js +11 -0
  42. package/esm/src/tool/index.d.ts +3 -1
  43. package/esm/src/tool/index.d.ts.map +1 -1
  44. package/esm/src/tool/index.js +1 -0
  45. package/esm/src/tool/remote-mcp.d.ts +14 -0
  46. package/esm/src/tool/remote-mcp.d.ts.map +1 -0
  47. package/esm/src/tool/remote-mcp.js +176 -0
  48. package/esm/src/tool/types.d.ts +12 -0
  49. package/esm/src/tool/types.d.ts.map +1 -1
  50. package/esm/src/utils/version-constant.d.ts +1 -1
  51. package/esm/src/utils/version-constant.js +1 -1
  52. package/package.json +1 -1
  53. package/src/deno.js +1 -1
  54. package/src/src/agent/ag-ui-handler.ts +451 -0
  55. package/src/src/agent/index.ts +21 -1
  56. package/src/src/agent/runtime/index.ts +28 -3
  57. package/src/src/agent/runtime/resume-session.ts +319 -0
  58. package/src/src/agent/runtime/tool-helpers.ts +89 -6
  59. package/src/src/agent/types.ts +2 -1
  60. package/src/src/internal-agents/session-manager.ts +69 -243
  61. package/src/src/mcp/index.ts +3 -0
  62. package/src/src/mcp/server.ts +54 -9
  63. package/src/src/mcp/session.ts +31 -0
  64. package/src/src/mcp/sse.ts +19 -0
  65. package/src/src/platform/cloud/resolver.ts +15 -3
  66. package/src/src/provider/index.ts +5 -0
  67. package/src/src/provider/veryfront-cloud/context.ts +28 -0
  68. package/src/src/tool/index.ts +9 -1
  69. package/src/src/tool/remote-mcp.ts +261 -0
  70. package/src/src/tool/types.ts +17 -0
  71. package/src/src/utils/version-constant.ts +1 -1
@@ -0,0 +1,319 @@
1
+ import * as dntShim from "../../../_dnt.shims.js";
2
+ export type RunSessionStatus = "running" | "waiting" | "completed" | "cancelled" | "failed";
3
+
4
+ export class RunCancelledError extends Error {
5
+ constructor(message = "Run cancelled") {
6
+ super(message);
7
+ this.name = "RunCancelledError";
8
+ }
9
+ }
10
+
11
+ export class RunAlreadyExistsError extends Error {
12
+ constructor(runId: string) {
13
+ super(`Run "${runId}" is already active`);
14
+ this.name = "RunAlreadyExistsError";
15
+ }
16
+ }
17
+
18
+ export class RunNotActiveError extends Error {
19
+ constructor(runId: string) {
20
+ super(`Run "${runId}" is not active`);
21
+ this.name = "RunNotActiveError";
22
+ }
23
+ }
24
+
25
+ export class WaitNotPendingError extends Error {
26
+ constructor(runId: string, waitKey: string) {
27
+ super(`Run "${runId}" is not waiting for "${waitKey}"`);
28
+ this.name = "WaitNotPendingError";
29
+ }
30
+ }
31
+
32
+ export class WaitConflictError extends Error {
33
+ constructor(runId: string, waitKey: string) {
34
+ super(`Conflicting resume value for run "${runId}" and wait key "${waitKey}"`);
35
+ this.name = "WaitConflictError";
36
+ }
37
+ }
38
+
39
+ export interface SubmitResumeValueOutcome {
40
+ accepted: true;
41
+ duplicate?: true;
42
+ }
43
+
44
+ type SubmittedValue<T> = {
45
+ value: T;
46
+ key: string;
47
+ };
48
+
49
+ type WaitingState<T> = {
50
+ waitKey: string;
51
+ resolve: (value: SubmittedValue<T>) => void;
52
+ reject: (reason?: unknown) => void;
53
+ };
54
+
55
+ type RunSession<T> = {
56
+ runId: string;
57
+ status: RunSessionStatus;
58
+ abortController: AbortController;
59
+ waitingState: WaitingState<T> | null;
60
+ submittedValues: Map<string, SubmittedValue<T>>;
61
+ waitingTimeoutId: number | null;
62
+ sessionTimeoutId: number | null;
63
+ };
64
+
65
+ const DEFAULT_WAITING_TTL_MS = 5 * 60 * 1000;
66
+ const DEFAULT_MAX_CONCURRENT_SESSIONS = 100;
67
+
68
+ export interface RunResumeSessionManagerOptions<T> {
69
+ waitingTtlMs?: number;
70
+ sessionTtlMs?: number | null;
71
+ maxConcurrentSessions?: number;
72
+ setTimeoutFn?: typeof dntShim.setTimeout;
73
+ clearTimeoutFn?: typeof clearTimeout;
74
+ getConflictKey?: (value: T) => string;
75
+ }
76
+
77
+ function defaultConflictKey(value: unknown): string {
78
+ return JSON.stringify(value);
79
+ }
80
+
81
+ export class RunResumeSessionManager<T> {
82
+ private readonly sessions = new Map<string, RunSession<T>>();
83
+
84
+ constructor(
85
+ private readonly options: RunResumeSessionManagerOptions<T> = {},
86
+ ) {}
87
+
88
+ private get waitingTtlMs(): number {
89
+ return this.options.waitingTtlMs ?? DEFAULT_WAITING_TTL_MS;
90
+ }
91
+
92
+ private get sessionTtlMs(): number | null {
93
+ return this.options.sessionTtlMs ?? null;
94
+ }
95
+
96
+ private get maxConcurrentSessions(): number {
97
+ return this.options.maxConcurrentSessions ?? DEFAULT_MAX_CONCURRENT_SESSIONS;
98
+ }
99
+
100
+ private get setTimeoutFn(): typeof dntShim.setTimeout {
101
+ return this.options.setTimeoutFn ?? dntShim.dntGlobalThis.setTimeout.bind(dntShim.dntGlobalThis);
102
+ }
103
+
104
+ private get clearTimeoutFn(): typeof clearTimeout {
105
+ return this.options.clearTimeoutFn ?? globalThis.clearTimeout.bind(dntShim.dntGlobalThis);
106
+ }
107
+
108
+ private getConflictKey(value: T): string {
109
+ const createConflictKey = this.options.getConflictKey ?? defaultConflictKey;
110
+ return createConflictKey(value);
111
+ }
112
+
113
+ private clearWaitingTimeout(session: RunSession<T>): void {
114
+ if (session.waitingTimeoutId === null) return;
115
+ this.clearTimeoutFn(session.waitingTimeoutId);
116
+ session.waitingTimeoutId = null;
117
+ }
118
+
119
+ private clearSessionTimeout(session: RunSession<T>): void {
120
+ if (session.sessionTimeoutId === null) return;
121
+ this.clearTimeoutFn(session.sessionTimeoutId);
122
+ session.sessionTimeoutId = null;
123
+ }
124
+
125
+ private scheduleSessionTimeout(session: RunSession<T>): void {
126
+ if (this.sessionTtlMs === null) return;
127
+ this.clearSessionTimeout(session);
128
+ session.sessionTimeoutId = this.setTimeoutFn(() => {
129
+ this.cancelRun(session.runId);
130
+ }, this.sessionTtlMs) as unknown as number;
131
+ }
132
+
133
+ private scheduleWaitingTimeout(session: RunSession<T>): void {
134
+ this.clearWaitingTimeout(session);
135
+ session.waitingTimeoutId = this.setTimeoutFn(() => {
136
+ this.cancelRun(session.runId);
137
+ }, this.waitingTtlMs) as unknown as number;
138
+ }
139
+
140
+ private touchSession(session: RunSession<T>): void {
141
+ if (session.status === "running" || session.status === "waiting") {
142
+ this.scheduleSessionTimeout(session);
143
+ }
144
+ }
145
+
146
+ private finalizeSession(
147
+ session: RunSession<T>,
148
+ status: Exclude<RunSessionStatus, "running" | "waiting">,
149
+ ): void {
150
+ session.status = status;
151
+ this.clearWaitingTimeout(session);
152
+ this.clearSessionTimeout(session);
153
+ session.waitingState = null;
154
+ this.sessions.delete(session.runId);
155
+ }
156
+
157
+ startRun(input: { runId: string; threadId: string }): AbortSignal {
158
+ const existing = this.sessions.get(input.runId);
159
+ if (existing && (existing.status === "running" || existing.status === "waiting")) {
160
+ throw new RunAlreadyExistsError(input.runId);
161
+ }
162
+
163
+ if (this.sessions.size >= this.maxConcurrentSessions) {
164
+ throw new Error(
165
+ `Maximum concurrent sessions (${this.maxConcurrentSessions}) reached`,
166
+ );
167
+ }
168
+
169
+ const session: RunSession<T> = {
170
+ runId: input.runId,
171
+ status: "running",
172
+ abortController: new AbortController(),
173
+ waitingState: null,
174
+ submittedValues: new Map(),
175
+ waitingTimeoutId: null,
176
+ sessionTimeoutId: null,
177
+ };
178
+
179
+ this.sessions.set(input.runId, session);
180
+ this.touchSession(session);
181
+ return session.abortController.signal;
182
+ }
183
+
184
+ async waitForSignal(runId: string, waitKey: string): Promise<T> {
185
+ const session = this.sessions.get(runId);
186
+ if (!session || session.status === "completed" || session.status === "failed") {
187
+ throw new RunNotActiveError(runId);
188
+ }
189
+
190
+ if (session.abortController.signal.aborted || session.status === "cancelled") {
191
+ throw new RunCancelledError();
192
+ }
193
+
194
+ const existingValue = session.submittedValues.get(waitKey);
195
+ if (existingValue) {
196
+ session.status = "running";
197
+ this.touchSession(session);
198
+ return existingValue.value;
199
+ }
200
+
201
+ if (session.waitingState && session.waitingState.waitKey !== waitKey) {
202
+ throw new WaitNotPendingError(runId, waitKey);
203
+ }
204
+
205
+ session.status = "waiting";
206
+ this.scheduleWaitingTimeout(session);
207
+ this.touchSession(session);
208
+
209
+ return await new Promise<T>((resolve, reject) => {
210
+ const abortHandler = () => {
211
+ this.clearWaitingTimeout(session);
212
+ session.waitingState = null;
213
+ session.status = "cancelled";
214
+ reject(new RunCancelledError());
215
+ };
216
+
217
+ session.abortController.signal.addEventListener("abort", abortHandler, { once: true });
218
+ session.waitingState = {
219
+ waitKey,
220
+ resolve: (value) => {
221
+ session.abortController.signal.removeEventListener("abort", abortHandler);
222
+ this.clearWaitingTimeout(session);
223
+ session.waitingState = null;
224
+ session.status = "running";
225
+ this.touchSession(session);
226
+ resolve(value.value);
227
+ },
228
+ reject: (reason) => {
229
+ session.abortController.signal.removeEventListener("abort", abortHandler);
230
+ this.clearWaitingTimeout(session);
231
+ session.waitingState = null;
232
+ reject(reason);
233
+ },
234
+ };
235
+ });
236
+ }
237
+
238
+ submitSignal(
239
+ runId: string,
240
+ input: { waitKey: string; value: T },
241
+ ): SubmitResumeValueOutcome {
242
+ const session = this.sessions.get(runId);
243
+ if (!session) {
244
+ throw new RunNotActiveError(runId);
245
+ }
246
+
247
+ const normalized: SubmittedValue<T> = {
248
+ value: input.value,
249
+ key: this.getConflictKey(input.value),
250
+ };
251
+
252
+ const existingValue = session.submittedValues.get(input.waitKey);
253
+ if (existingValue) {
254
+ if (existingValue.key === normalized.key) {
255
+ return { accepted: true, duplicate: true };
256
+ }
257
+
258
+ throw new WaitConflictError(runId, input.waitKey);
259
+ }
260
+
261
+ if (
262
+ session.status === "completed" || session.status === "failed" ||
263
+ session.status === "cancelled"
264
+ ) {
265
+ throw new RunNotActiveError(runId);
266
+ }
267
+
268
+ if (!session.waitingState || session.waitingState.waitKey !== input.waitKey) {
269
+ throw new WaitNotPendingError(runId, input.waitKey);
270
+ }
271
+
272
+ session.submittedValues.set(input.waitKey, normalized);
273
+ this.touchSession(session);
274
+ session.waitingState.resolve(normalized);
275
+ return { accepted: true };
276
+ }
277
+
278
+ cancelRun(runId: string): boolean {
279
+ const session = this.sessions.get(runId);
280
+ if (!session) return false;
281
+
282
+ if (
283
+ session.status === "completed" || session.status === "failed" ||
284
+ session.status === "cancelled"
285
+ ) {
286
+ return false;
287
+ }
288
+
289
+ const waitingState = session.waitingState;
290
+ session.abortController.abort(new RunCancelledError());
291
+ waitingState?.reject(new RunCancelledError());
292
+ this.finalizeSession(session, "cancelled");
293
+ return true;
294
+ }
295
+
296
+ completeRun(runId: string): void {
297
+ const session = this.sessions.get(runId);
298
+ if (!session) return;
299
+ this.finalizeSession(session, "completed");
300
+ }
301
+
302
+ failRun(runId: string): void {
303
+ const session = this.sessions.get(runId);
304
+ if (!session) return;
305
+ this.finalizeSession(session, "failed");
306
+ }
307
+
308
+ getRunStatus(runId: string): RunSessionStatus | null {
309
+ return this.sessions.get(runId)?.status ?? null;
310
+ }
311
+
312
+ reset(): void {
313
+ for (const session of this.sessions.values()) {
314
+ this.clearWaitingTimeout(session);
315
+ this.clearSessionTimeout(session);
316
+ }
317
+ this.sessions.clear();
318
+ }
319
+ }
@@ -6,7 +6,7 @@
6
6
  * @module ai/agent/runtime/tool-helpers
7
7
  */
8
8
 
9
- import type { Tool, ToolDefinition, ToolExecutionContext } from "../../tool/index.js";
9
+ import type { RemoteToolSource, Tool, ToolDefinition, ToolExecutionContext } from "../../tool/index.js";
10
10
  import { executeTool, toolRegistry } from "../../tool/index.js";
11
11
  import { toolToProviderDefinition } from "../../tool/registry.js";
12
12
  import { SKILL_TOOL_IDS } from "../../skill/types.js";
@@ -101,21 +101,90 @@ function throwUnknownConfiguredToolsError(
101
101
  async function getRemoteToolDefinitions(options?: {
102
102
  includeIntegrationTools?: boolean;
103
103
  allowedRemoteToolNames?: string[];
104
+ remoteToolSources?: RemoteToolSource[];
105
+ remoteToolContext?: ToolExecutionContext;
104
106
  }): Promise<ToolDefinition[]> {
107
+ const remoteToolContext = options?.remoteToolContext;
108
+ const definitions: ToolDefinition[] = [];
109
+ const seenToolNames = new Set<string>();
110
+
111
+ const addDefinition = (definition: ToolDefinition): void => {
112
+ if (seenToolNames.has(definition.name)) {
113
+ return;
114
+ }
115
+ if (
116
+ options?.allowedRemoteToolNames &&
117
+ !options.allowedRemoteToolNames.includes(definition.name)
118
+ ) {
119
+ return;
120
+ }
121
+ seenToolNames.add(definition.name);
122
+ definitions.push(definition);
123
+ };
124
+
125
+ for (const source of options?.remoteToolSources ?? []) {
126
+ try {
127
+ const sourceDefs = await source.listTools(remoteToolContext);
128
+ for (const def of sourceDefs) {
129
+ addDefinition(def);
130
+ }
131
+ } catch (error) {
132
+ logger.warn("Failed to fetch remote tool definitions from source", {
133
+ sourceId: source.id,
134
+ error: error instanceof Error ? error.message : String(error),
135
+ });
136
+ }
137
+ }
138
+
105
139
  if (options?.includeIntegrationTools === false) {
106
- return [];
140
+ return definitions;
107
141
  }
108
142
 
109
143
  try {
110
144
  const { getRemoteIntegrationToolDefinitions } = await import(
111
145
  "../../integrations/remote-tools.js"
112
146
  );
113
- return (await getRemoteIntegrationToolDefinitions()).filter((def) =>
114
- !options?.allowedRemoteToolNames || options.allowedRemoteToolNames.includes(def.name)
115
- );
147
+ for (const def of await getRemoteIntegrationToolDefinitions()) {
148
+ addDefinition(def);
149
+ }
116
150
  } catch {
117
- return [];
151
+ return definitions;
152
+ }
153
+
154
+ return definitions;
155
+ }
156
+
157
+ async function sourceHasTool(
158
+ source: RemoteToolSource,
159
+ toolName: string,
160
+ context?: ToolExecutionContext,
161
+ ): Promise<boolean> {
162
+ return (await source.listTools(context)).some((definition) => definition.name === toolName);
163
+ }
164
+
165
+ async function executeRemoteToolFromSources(
166
+ toolName: string,
167
+ input: Record<string, unknown>,
168
+ context: ToolExecutionContext | undefined,
169
+ allowedRemoteToolNames: string[] | undefined,
170
+ remoteToolSources: RemoteToolSource[] | undefined,
171
+ ): Promise<{ handled: boolean; result?: unknown }> {
172
+ for (const source of remoteToolSources ?? []) {
173
+ if (!(await sourceHasTool(source, toolName, context))) {
174
+ continue;
175
+ }
176
+
177
+ if (allowedRemoteToolNames && !allowedRemoteToolNames.includes(toolName)) {
178
+ throw new Error(`Tool "${toolName}" is not allowed for this run`);
179
+ }
180
+
181
+ return {
182
+ handled: true,
183
+ result: await source.executeTool(toolName, input, context),
184
+ };
118
185
  }
186
+
187
+ return { handled: false };
119
188
  }
120
189
 
121
190
  export function resolveConfiguredTool(
@@ -148,6 +217,7 @@ export async function executeConfiguredTool(
148
217
  toolsConfig: true | Record<string, ToolConfigEntry> | undefined,
149
218
  context?: ToolExecutionContext,
150
219
  allowedRemoteToolNames?: string[],
220
+ remoteToolSources?: RemoteToolSource[],
151
221
  ): Promise<unknown> {
152
222
  const configuredTool = resolveConfiguredTool(toolsConfig, toolName);
153
223
  if (configuredTool) {
@@ -160,6 +230,17 @@ export async function executeConfiguredTool(
160
230
  return await registryTool.execute(input, context);
161
231
  }
162
232
 
233
+ const remoteSourceResult = await executeRemoteToolFromSources(
234
+ toolName,
235
+ input,
236
+ context,
237
+ allowedRemoteToolNames,
238
+ remoteToolSources,
239
+ );
240
+ if (remoteSourceResult.handled) {
241
+ return remoteSourceResult.result;
242
+ }
243
+
163
244
  // Fall back to remote execution for integration tools (e.g., github:list-repos)
164
245
  if (isRemoteIntegrationTool(toolName)) {
165
246
  if (allowedRemoteToolNames && !allowedRemoteToolNames.includes(toolName)) {
@@ -207,6 +288,8 @@ export async function getAvailableTools(
207
288
  includeSkillTools?: boolean;
208
289
  includeIntegrationTools?: boolean;
209
290
  allowedRemoteToolNames?: string[];
291
+ remoteToolSources?: RemoteToolSource[];
292
+ remoteToolContext?: ToolExecutionContext;
210
293
  },
211
294
  ): Promise<ToolDefinition[]> {
212
295
  if (!toolsConfig) return [];
@@ -4,7 +4,7 @@
4
4
  import * as dntShim from "../../_dnt.shims.js";
5
5
 
6
6
 
7
- import type { Tool } from "../tool/index.js";
7
+ import type { RemoteToolSource, Tool } from "../tool/index.js";
8
8
  import { INVALID_ARGUMENT } from "../errors/error-registry.js";
9
9
  import type { Memory } from "./memory/memory-interface.js";
10
10
 
@@ -79,6 +79,7 @@ export interface AgentConfig {
79
79
  model?: ModelString;
80
80
  system: string | (() => string) | (() => Promise<string>);
81
81
  tools?: true | Record<string, Tool | boolean>;
82
+ remoteTools?: RemoteToolSource[];
82
83
  maxSteps?: number;
83
84
  streaming?: boolean;
84
85
  memory?: MemoryConfig;