@vertesia/tools-sdk 1.6.0-dev.20261004.085740Z → 1.6.0-dev.20261009.095813Z
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/lib/SkillCollection.d.ts +0 -18
- package/lib/SkillCollection.d.ts.map +1 -1
- package/lib/SkillCollection.js +0 -58
- package/lib/SkillCollection.js.map +1 -1
- package/lib/ToolCollection.d.ts +0 -18
- package/lib/ToolCollection.d.ts.map +1 -1
- package/lib/ToolCollection.js +0 -64
- package/lib/ToolCollection.js.map +1 -1
- package/lib/auth.d.ts +7 -1
- package/lib/auth.d.ts.map +1 -1
- package/lib/auth.js +22 -1
- package/lib/auth.js.map +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/lib/node-loaders.d.ts +38 -0
- package/lib/node-loaders.d.ts.map +1 -0
- package/lib/node-loaders.js +122 -0
- package/lib/node-loaders.js.map +1 -0
- package/lib/sandbox.d.ts +30 -0
- package/lib/sandbox.d.ts.map +1 -0
- package/lib/sandbox.js +37 -0
- package/lib/sandbox.js.map +1 -0
- package/lib/scratch.d.ts +44 -0
- package/lib/scratch.d.ts.map +1 -0
- package/lib/scratch.js +157 -0
- package/lib/scratch.js.map +1 -0
- package/lib/server/hooks.d.ts.map +1 -1
- package/lib/server/hooks.js +2 -0
- package/lib/server/hooks.js.map +1 -1
- package/lib/types.d.ts +6 -0
- package/lib/types.d.ts.map +1 -1
- package/package.json +12 -8
- package/src/SkillCollection.ts +0 -63
- package/src/ToolCollection.ts +0 -68
- package/src/auth.ts +25 -0
- package/src/index.ts +2 -0
- package/src/node-loaders.ts +131 -0
- package/src/sandbox.ts +52 -0
- package/src/scratch.ts +183 -0
- package/src/server/hooks.ts +2 -0
- package/src/types.ts +6 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { parseSkillFile } from './SkillCollection.js';
|
|
5
|
+
import type { SkillContentType, SkillDefinition, Tool } from './types.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Load all tools from a directory.
|
|
9
|
+
* Scans for .js files and imports tools that match naming convention.
|
|
10
|
+
*
|
|
11
|
+
* Directory structure:
|
|
12
|
+
* ```
|
|
13
|
+
* collection/
|
|
14
|
+
* tools/
|
|
15
|
+
* SearchFundsTool.js # exports SearchFundsTool
|
|
16
|
+
* GetFundDetailsTool.js # exports GetFundDetailsTool
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* Naming convention: File should export a Tool with name matching *Tool pattern.
|
|
20
|
+
*
|
|
21
|
+
* @param toolsDir - Path to the tools directory (e.g., /path/to/collection/tools)
|
|
22
|
+
* @returns Promise resolving to array of Tool objects
|
|
23
|
+
*/
|
|
24
|
+
export async function loadToolsFromDirectory(toolsDir: string): Promise<Tool[]> {
|
|
25
|
+
const tools: Tool[] = [];
|
|
26
|
+
|
|
27
|
+
if (!existsSync(toolsDir)) {
|
|
28
|
+
console.warn(`Tools directory not found: ${toolsDir}`);
|
|
29
|
+
return tools;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let entries: string[];
|
|
33
|
+
try {
|
|
34
|
+
entries = readdirSync(toolsDir);
|
|
35
|
+
} catch {
|
|
36
|
+
console.warn(`Could not read tools directory: ${toolsDir}`);
|
|
37
|
+
return tools;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
// Only process .js and .ts files that end with Tool
|
|
42
|
+
if (!entry.endsWith('Tool.js') && !entry.endsWith('Tool.ts')) continue;
|
|
43
|
+
if (entry.endsWith('.d.ts')) continue;
|
|
44
|
+
|
|
45
|
+
const entryPath = join(toolsDir, entry);
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const stat = statSync(entryPath);
|
|
49
|
+
if (!stat.isFile()) continue;
|
|
50
|
+
|
|
51
|
+
// Dynamic import - need file:// URL for ESM
|
|
52
|
+
const fileUrl = pathToFileURL(entryPath).href;
|
|
53
|
+
const module = await import(fileUrl);
|
|
54
|
+
|
|
55
|
+
// Find exported Tool (named export matching filename or any Tool export)
|
|
56
|
+
const baseName = entry.replace(/\.(js|ts)$/, '');
|
|
57
|
+
const tool = module[baseName] || module.default;
|
|
58
|
+
|
|
59
|
+
if (tool && typeof tool.name === 'string' && typeof tool.run === 'function') {
|
|
60
|
+
tools.push(tool);
|
|
61
|
+
} else {
|
|
62
|
+
console.warn(`No valid Tool export found in ${entry}`);
|
|
63
|
+
}
|
|
64
|
+
} catch (err) {
|
|
65
|
+
console.warn(`Error loading tool from ${entry}:`, err);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return tools;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Load all skills from a directory.
|
|
74
|
+
* Scans for subdirectories containing SKILL.md files.
|
|
75
|
+
*
|
|
76
|
+
* Directory structure:
|
|
77
|
+
* ```
|
|
78
|
+
* skills/
|
|
79
|
+
* nagare/
|
|
80
|
+
* fund-onboarding/
|
|
81
|
+
* SKILL.md
|
|
82
|
+
* monte-carlo/
|
|
83
|
+
* SKILL.md
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* @param dirPath - Path to the skills collection directory
|
|
87
|
+
* @returns Array of parsed skill definitions
|
|
88
|
+
*/
|
|
89
|
+
export function loadSkillsFromDirectory(dirPath: string): SkillDefinition[] {
|
|
90
|
+
const skills: SkillDefinition[] = [];
|
|
91
|
+
|
|
92
|
+
let entries: string[];
|
|
93
|
+
try {
|
|
94
|
+
entries = readdirSync(dirPath);
|
|
95
|
+
} catch {
|
|
96
|
+
console.warn(`Could not read skills directory: ${dirPath}`);
|
|
97
|
+
return skills;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
for (const entry of entries) {
|
|
101
|
+
const entryPath = join(dirPath, entry);
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const stat = statSync(entryPath);
|
|
105
|
+
if (!stat.isDirectory()) continue;
|
|
106
|
+
|
|
107
|
+
// Look for SKILL.md or SKILL.jst
|
|
108
|
+
const mdPath = join(entryPath, 'SKILL.md');
|
|
109
|
+
const jstPath = join(entryPath, 'SKILL.jst');
|
|
110
|
+
|
|
111
|
+
let content: string | undefined;
|
|
112
|
+
let contentType: SkillContentType = 'md';
|
|
113
|
+
|
|
114
|
+
if (existsSync(mdPath)) {
|
|
115
|
+
content = readFileSync(mdPath, 'utf-8');
|
|
116
|
+
contentType = 'md';
|
|
117
|
+
} else if (existsSync(jstPath)) {
|
|
118
|
+
content = readFileSync(jstPath, 'utf-8');
|
|
119
|
+
contentType = 'jst';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (content) {
|
|
123
|
+
skills.push(parseSkillFile(content, contentType));
|
|
124
|
+
}
|
|
125
|
+
} catch (err) {
|
|
126
|
+
console.warn(`Error loading skill from ${entryPath}:`, err);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return skills;
|
|
131
|
+
}
|
package/src/sandbox.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { AuthTokenPayload } from '@vertesia/common';
|
|
2
|
+
import { Hono } from 'hono';
|
|
3
|
+
import { AuthSession } from './auth.js';
|
|
4
|
+
import { createSandboxScratch } from './scratch.js';
|
|
5
|
+
import type { ToolServerConfig } from './server/types.js';
|
|
6
|
+
import { createToolServer as createBaseToolServer } from './server.js';
|
|
7
|
+
|
|
8
|
+
/** Browser-bundle entrypoint. Collections take bundled data; directory loaders are Node-only. */
|
|
9
|
+
export * from './ActivityCollection.js';
|
|
10
|
+
export { AuthSession, authorizeSandbox as authorize } from './auth.js';
|
|
11
|
+
export * from './ContentTypesCollection.js';
|
|
12
|
+
export * from './InteractionCollection.js';
|
|
13
|
+
export * from './RenderingTemplateCollection.js';
|
|
14
|
+
export * from './SkillCollection.js';
|
|
15
|
+
export * from './scratch.js';
|
|
16
|
+
export type { BuildAppPackageOptions } from './server/app-package.js';
|
|
17
|
+
export { buildAppPackage } from './server/app-package.js';
|
|
18
|
+
export * from './server/types.js';
|
|
19
|
+
/** The host supplies non-secret claims; SDK HTTP goes through the sandbox's host-mediated fetch. */
|
|
20
|
+
export function createToolServer(config: ToolServerConfig) {
|
|
21
|
+
const app = new Hono<{
|
|
22
|
+
Bindings: { sandboxSession?: AuthTokenPayload };
|
|
23
|
+
Variables: { toolAuthSession: AuthSession; toolAuthMode: 'sandbox' };
|
|
24
|
+
}>();
|
|
25
|
+
app.use('*', async (ctx, next) => {
|
|
26
|
+
ctx.set('toolAuthMode', 'sandbox');
|
|
27
|
+
const payload = ctx.env?.sandboxSession;
|
|
28
|
+
if (payload) {
|
|
29
|
+
ctx.set(
|
|
30
|
+
'toolAuthSession',
|
|
31
|
+
new AuthSession(
|
|
32
|
+
'sandbox',
|
|
33
|
+
payload,
|
|
34
|
+
{
|
|
35
|
+
studio: 'https://platform.invalid/studio',
|
|
36
|
+
store: 'https://platform.invalid/store',
|
|
37
|
+
token: 'https://platform.invalid/token',
|
|
38
|
+
},
|
|
39
|
+
undefined,
|
|
40
|
+
createSandboxScratch(),
|
|
41
|
+
),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
await next();
|
|
45
|
+
});
|
|
46
|
+
app.route('/', createBaseToolServer(config));
|
|
47
|
+
return app;
|
|
48
|
+
}
|
|
49
|
+
export * from './site/templates.js';
|
|
50
|
+
export * from './ToolCollection.js';
|
|
51
|
+
export * from './ToolRegistry.js';
|
|
52
|
+
export * from './types.js';
|
package/src/scratch.ts
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-invocation scratch storage with one API in every host. Under the Vertesia workerd sandbox it is served
|
|
3
|
+
* by the runtime; on Node (localhost, Vercel) it lives in memory for the request. Either way it is private to
|
|
4
|
+
* one tool request and gone when the request ends: keep durable results in agent artifacts or files.
|
|
5
|
+
*
|
|
6
|
+
* Browser-safe: no Node built-ins, so the sandbox bundle can include it.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type ScratchData = string | Uint8Array | ArrayBuffer | Blob;
|
|
10
|
+
|
|
11
|
+
export interface ScratchEntry {
|
|
12
|
+
key: string;
|
|
13
|
+
size: number;
|
|
14
|
+
contentType: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ToolScratch {
|
|
18
|
+
/** Create or replace `key`. Keys are `/`-separated relative paths, e.g. `work/page-1.json`. */
|
|
19
|
+
put(key: string, data: ScratchData, options?: { contentType?: string }): Promise<void>;
|
|
20
|
+
putJson(key: string, value: unknown): Promise<void>;
|
|
21
|
+
/** `undefined` when the key does not exist. */
|
|
22
|
+
get(key: string): Promise<Uint8Array | undefined>;
|
|
23
|
+
getText(key: string): Promise<string | undefined>;
|
|
24
|
+
getJson<T = unknown>(key: string): Promise<T | undefined>;
|
|
25
|
+
/** `false` when the key did not exist. */
|
|
26
|
+
delete(key: string): Promise<boolean>;
|
|
27
|
+
list(): Promise<ScratchEntry[]>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type ScratchErrorCode = 'invalid_key' | 'quota_exceeded' | 'unavailable';
|
|
31
|
+
|
|
32
|
+
export class ScratchError extends Error {
|
|
33
|
+
constructor(
|
|
34
|
+
public readonly code: ScratchErrorCode,
|
|
35
|
+
message: string,
|
|
36
|
+
options?: { cause?: unknown },
|
|
37
|
+
) {
|
|
38
|
+
super(message, options);
|
|
39
|
+
this.name = 'ScratchError';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Matches the sandbox runtime's per-invocation default (`APP_RUNTIME_MAX_SCRATCH_BYTES`). */
|
|
44
|
+
export const DEFAULT_SCRATCH_QUOTA_BYTES = 128 * 1024 * 1024;
|
|
45
|
+
|
|
46
|
+
/** Guest-visible origin of the sandbox runtime's scratch storage. */
|
|
47
|
+
export const SANDBOX_SCRATCH_ORIGIN = 'http://scratch.invalid';
|
|
48
|
+
|
|
49
|
+
export function validateScratchKey(key: string): void {
|
|
50
|
+
if (
|
|
51
|
+
typeof key !== 'string' ||
|
|
52
|
+
!key ||
|
|
53
|
+
key.length > 1024 ||
|
|
54
|
+
/[\\\0]/.test(key) ||
|
|
55
|
+
key.split('/').some((part) => part === '.' || part === '..' || !part)
|
|
56
|
+
)
|
|
57
|
+
throw new ScratchError('invalid_key', `Invalid scratch key: ${JSON.stringify(key)}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Always a private copy: later caller mutations cannot change stored content in either backend. */
|
|
61
|
+
async function toBytes(data: ScratchData): Promise<Uint8Array<ArrayBuffer>> {
|
|
62
|
+
if (typeof data === 'string') return new TextEncoder().encode(data);
|
|
63
|
+
if (data instanceof Uint8Array) return data.slice();
|
|
64
|
+
if (data instanceof ArrayBuffer) return new Uint8Array(data.slice(0));
|
|
65
|
+
return new Uint8Array(await data.arrayBuffer());
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function contentTypeOf(data: ScratchData, contentType: string | undefined): string {
|
|
69
|
+
if (contentType) return contentType;
|
|
70
|
+
if (typeof data === 'string') return 'text/plain;charset=UTF-8';
|
|
71
|
+
if (typeof Blob !== 'undefined' && data instanceof Blob && data.type) return data.type;
|
|
72
|
+
return 'application/octet-stream';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Shared convenience methods over the three primitives each backend implements. */
|
|
76
|
+
function withHelpers(base: {
|
|
77
|
+
put(key: string, bytes: Uint8Array<ArrayBuffer>, contentType: string): Promise<void>;
|
|
78
|
+
get(key: string): Promise<Uint8Array | undefined>;
|
|
79
|
+
delete(key: string): Promise<boolean>;
|
|
80
|
+
list(): Promise<ScratchEntry[]>;
|
|
81
|
+
}): ToolScratch {
|
|
82
|
+
const scratch: ToolScratch = {
|
|
83
|
+
async put(key, data, options) {
|
|
84
|
+
validateScratchKey(key);
|
|
85
|
+
await base.put(key, await toBytes(data), contentTypeOf(data, options?.contentType));
|
|
86
|
+
},
|
|
87
|
+
putJson: (key, value) => scratch.put(key, JSON.stringify(value), { contentType: 'application/json' }),
|
|
88
|
+
async get(key) {
|
|
89
|
+
validateScratchKey(key);
|
|
90
|
+
return base.get(key);
|
|
91
|
+
},
|
|
92
|
+
async getText(key) {
|
|
93
|
+
const bytes = await scratch.get(key);
|
|
94
|
+
return bytes === undefined ? undefined : new TextDecoder().decode(bytes);
|
|
95
|
+
},
|
|
96
|
+
async getJson<T>(key: string) {
|
|
97
|
+
const text = await scratch.getText(key);
|
|
98
|
+
return text === undefined ? undefined : (JSON.parse(text) as T);
|
|
99
|
+
},
|
|
100
|
+
async delete(key) {
|
|
101
|
+
validateScratchKey(key);
|
|
102
|
+
return base.delete(key);
|
|
103
|
+
},
|
|
104
|
+
list: () => base.list(),
|
|
105
|
+
};
|
|
106
|
+
return scratch;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** In-process scratch for Node hosts. One instance per request; it is garbage collected with the request. */
|
|
110
|
+
export function createMemoryScratch(quota = defaultMemoryQuota()): ToolScratch {
|
|
111
|
+
const entries = new Map<string, { bytes: Uint8Array; contentType: string }>();
|
|
112
|
+
let used = 0;
|
|
113
|
+
return withHelpers({
|
|
114
|
+
async put(key, bytes, contentType) {
|
|
115
|
+
const previous = entries.get(key)?.bytes.byteLength ?? 0;
|
|
116
|
+
if (bytes.byteLength > quota - used + previous)
|
|
117
|
+
throw new ScratchError('quota_exceeded', `Scratch storage quota of ${quota} bytes exceeded`);
|
|
118
|
+
entries.set(key, { bytes, contentType });
|
|
119
|
+
used += bytes.byteLength - previous;
|
|
120
|
+
},
|
|
121
|
+
async get(key) {
|
|
122
|
+
return entries.get(key)?.bytes.slice();
|
|
123
|
+
},
|
|
124
|
+
async delete(key) {
|
|
125
|
+
const entry = entries.get(key);
|
|
126
|
+
if (!entry) return false;
|
|
127
|
+
entries.delete(key);
|
|
128
|
+
used -= entry.bytes.byteLength;
|
|
129
|
+
return true;
|
|
130
|
+
},
|
|
131
|
+
async list() {
|
|
132
|
+
return [...entries].map(([key, { bytes, contentType }]) => ({ key, size: bytes.byteLength, contentType }));
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function defaultMemoryQuota(): number {
|
|
138
|
+
const configured = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env
|
|
139
|
+
?.VERTESIA_SCRATCH_MAX_BYTES;
|
|
140
|
+
const value = configured === undefined ? DEFAULT_SCRATCH_QUOTA_BYTES : Number(configured);
|
|
141
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new Error('Invalid VERTESIA_SCRATCH_MAX_BYTES');
|
|
142
|
+
return value;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Scratch served by the Vertesia sandbox runtime; the runtime enforces the quota. */
|
|
146
|
+
export function createSandboxScratch(fetchImpl: typeof fetch = (...args) => fetch(...args)): ToolScratch {
|
|
147
|
+
const url = (key = '') => `${SANDBOX_SCRATCH_ORIGIN}/${key.split('/').map(encodeURIComponent).join('/')}`;
|
|
148
|
+
const failure = async (response: Response, operation: string) => {
|
|
149
|
+
const detail = (await response.text().catch(() => '')).slice(0, 200);
|
|
150
|
+
if (response.status === 507)
|
|
151
|
+
return new ScratchError('quota_exceeded', detail || 'Scratch storage quota exceeded');
|
|
152
|
+
if (response.status === 400) return new ScratchError('invalid_key', detail || 'Invalid scratch key');
|
|
153
|
+
return new ScratchError('unavailable', `Scratch ${operation} failed with status ${response.status}: ${detail}`);
|
|
154
|
+
};
|
|
155
|
+
return withHelpers({
|
|
156
|
+
async put(key, bytes, contentType) {
|
|
157
|
+
const response = await fetchImpl(url(key), {
|
|
158
|
+
method: 'PUT',
|
|
159
|
+
headers: { 'content-type': contentType },
|
|
160
|
+
body: bytes,
|
|
161
|
+
});
|
|
162
|
+
if (!response.ok) throw await failure(response, 'write');
|
|
163
|
+
},
|
|
164
|
+
async get(key) {
|
|
165
|
+
const response = await fetchImpl(url(key));
|
|
166
|
+
if (response.status === 404) return undefined;
|
|
167
|
+
if (!response.ok) throw await failure(response, 'read');
|
|
168
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
169
|
+
},
|
|
170
|
+
async delete(key) {
|
|
171
|
+
const response = await fetchImpl(url(key), { method: 'DELETE' });
|
|
172
|
+
if (response.status === 404) return false;
|
|
173
|
+
if (!response.ok) throw await failure(response, 'delete');
|
|
174
|
+
return true;
|
|
175
|
+
},
|
|
176
|
+
async list() {
|
|
177
|
+
const response = await fetchImpl(url());
|
|
178
|
+
if (!response.ok) throw await failure(response, 'list');
|
|
179
|
+
const body = (await response.json()) as { entries: { key: string; size: number; type: string }[] };
|
|
180
|
+
return body.entries.map(({ key, size, type }) => ({ key, size, contentType: type }));
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
}
|
package/src/server/hooks.ts
CHANGED
|
@@ -53,6 +53,7 @@ export function createHooksRoute(app: Hono, basePath: string, config: ToolServer
|
|
|
53
53
|
token: session.token,
|
|
54
54
|
payload: session.payload,
|
|
55
55
|
getClient: () => session.getClient(),
|
|
56
|
+
scratch: session.scratch,
|
|
56
57
|
metadata,
|
|
57
58
|
};
|
|
58
59
|
const result = await hook.handler(context);
|
|
@@ -68,6 +69,7 @@ async function executeEventHook(c: Context, requestContext: ToolContext, hook: A
|
|
|
68
69
|
token: session.token,
|
|
69
70
|
payload: session.payload,
|
|
70
71
|
getClient: () => session.getClient(),
|
|
72
|
+
scratch: session.scratch,
|
|
71
73
|
};
|
|
72
74
|
const result = await hook.handler(eventPayload, context);
|
|
73
75
|
|
package/src/types.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
ToolResultContent,
|
|
15
15
|
ToolResultMeta,
|
|
16
16
|
} from '@vertesia/common';
|
|
17
|
+
import type { ToolScratch } from './scratch.js';
|
|
17
18
|
|
|
18
19
|
export type { ToolExecutionMetadata };
|
|
19
20
|
export type AppEventSubscriptionDefinition = CommonAppEventSubscriptionDefinition;
|
|
@@ -58,6 +59,11 @@ export interface ToolExecutionContext {
|
|
|
58
59
|
* @returns a vertesia client instance
|
|
59
60
|
*/
|
|
60
61
|
getClient: () => Promise<VertesiaClient>;
|
|
62
|
+
/**
|
|
63
|
+
* Temporary storage private to this request, with the same behavior in the Vertesia sandbox and on
|
|
64
|
+
* Node hosts (localhost, Vercel). Discarded when the request ends.
|
|
65
|
+
*/
|
|
66
|
+
scratch: ToolScratch;
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
export type AppLifecycleHookName = 'install' | 'uninstall';
|