@proveanything/smartlinks 2.0.26 → 2.0.28

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.
@@ -26,6 +26,9 @@ export declare const AI_TOOL_NAMES: {
26
26
  readonly PDF_CREATE: "pdf.create";
27
27
  readonly PDF_FILL: "pdf.fill";
28
28
  readonly PDF_MERGE: "pdf.merge";
29
+ readonly PDF_INSPECT: "pdf.inspect";
30
+ readonly PDF_RENDER: "pdf.render";
31
+ readonly PDF_EXTRACT: "pdf.extract";
29
32
  readonly HTTP_REQUEST: "http.request";
30
33
  readonly TRANSLATE: "translate";
31
34
  };
package/dist/ai-tools.js CHANGED
@@ -23,6 +23,9 @@ export const AI_TOOL_NAMES = {
23
23
  PDF_CREATE: 'pdf.create',
24
24
  PDF_FILL: 'pdf.fill',
25
25
  PDF_MERGE: 'pdf.merge',
26
+ PDF_INSPECT: 'pdf.inspect',
27
+ PDF_RENDER: 'pdf.render',
28
+ PDF_EXTRACT: 'pdf.extract',
26
29
  HTTP_REQUEST: 'http.request',
27
30
  TRANSLATE: 'translate',
28
31
  };
@@ -58,6 +61,12 @@ export const BUILTIN_AI_TOOLS = [
58
61
  description: 'Fill an AcroForm PDF\'s fields from a { field: value } map → hosted URL.' },
59
62
  { name: 'pdf.merge', group: 'media', capabilities: ['media:pdf'],
60
63
  description: 'Merge several PDFs (by URL) into one → hosted URL.' },
64
+ { name: 'pdf.inspect', group: 'media', capabilities: ['web:read'],
65
+ description: 'Cheaply introspect a PDF before any AI call: page count/sizes, whether each page has an extractable text layer, whether a raster image is present, and a routing recommendation (text-native → cheap text pass; curve-only/raster → vision/OCR). Deterministic, no AI.' },
66
+ { name: 'pdf.render', group: 'media', capabilities: ['web:read'],
67
+ description: 'Rasterize a single PDF page to a PNG at a controllable DPI (e.g. 300 for small print) and return a hosted image URL — feed to a vision call or screenshot a page.' },
68
+ { name: 'pdf.extract', group: 'media', capabilities: ['web:read', 'ai:vision'],
69
+ description: 'Extract typed JSON from a PDF in one call given a schema and/or prompt. Auto-routes: text-layer pages use cheap text extraction; curve-only/raster pages are rendered and read with vision.' },
61
70
  { name: 'http.request', group: 'net', capabilities: ['net:http'],
62
71
  description: 'SSRF-guarded outbound HTTP(S) request to a public URL; returns status/headers/body.' },
63
72
  { name: 'translate', group: 'text', capabilities: ['ai:text'],
package/dist/api/ai.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ContentPart, FunctionCall, ToolCall, ChatMessage, ToolDefinition, ResponseTool, ResponseInputItem, ResponsesRequest, ResponsesResult, ResponsesStreamEvent, ChatCompletionRequest, ChatCompletionChoice, ChatCompletionResponse, ChatCompletionChunk, AIModel, AIModelListParams, AIModelListResponse, AgentRunRequest, AgentRunResult, AgentToolsQuery, AgentToolsResponse, SkillsListResponse, CatalogResponse, DocumentChunk, IndexDocumentRequest, IndexDocumentResponse, ConfigureAssistantRequest, ConfigureAssistantResponse, PublicChatRequest, PublicChatResponse, Session, RateLimitStatus, SessionStatistics, AiSession, AiSessionCreate, AiUsageReport, VoiceSessionRequest, VoiceSessionResponse, EphemeralTokenRequest, EphemeralTokenResponse, TranscriptionResponse, TTSRequest, GeneratePodcastRequest, PodcastScript, GeneratePodcastResponse, PodcastStatus, AIGenerateContentRequest, AIGenerateContentCandidate, AIGenerateContentResponse, AIGenerateImageRequest, AIGenerateImageResponse, AIGeneratedImage, AISearchPhotosRequest, AISearchPhotosPhoto, AISearchPhotosResponse, AIUploadedFile, AICacheRef } from "../types/ai.js";
1
+ import type { ContentPart, FunctionCall, ToolCall, ChatMessage, ToolDefinition, ResponseTool, ResponseInputItem, ResponsesRequest, ResponsesResult, ResponsesStreamEvent, ChatCompletionRequest, ChatCompletionChoice, ChatCompletionResponse, ChatCompletionChunk, AIModel, AIModelListParams, AIModelListResponse, AgentRunRequest, AgentRunResult, AgentToolsQuery, AgentToolsResponse, ToolRunResult, AiToolName, AiToolArgsMap, PublicAgentRunRequest, ClientTool, RunWithClientToolsOptions, SkillsListResponse, CatalogResponse, DocumentChunk, IndexDocumentRequest, IndexDocumentResponse, ConfigureAssistantRequest, ConfigureAssistantResponse, PublicChatRequest, PublicChatResponse, Session, RateLimitStatus, SessionStatistics, AiSession, AiSessionCreate, AiUsageReport, VoiceSessionRequest, VoiceSessionResponse, EphemeralTokenRequest, EphemeralTokenResponse, TranscriptionResponse, TTSRequest, GeneratePodcastRequest, PodcastScript, GeneratePodcastResponse, PodcastStatus, AIGenerateContentRequest, AIGenerateContentCandidate, AIGenerateContentResponse, AIGenerateImageRequest, AIGenerateImageResponse, AIGeneratedImage, AISearchPhotosRequest, AISearchPhotosPhoto, AISearchPhotosResponse, AIUploadedFile, AICacheRef } from "../types/ai.js";
2
2
  export type { ContentPart, FunctionCall, ToolCall, ChatMessage, ToolDefinition, ResponseTool, ResponseInputItem, ResponsesRequest, ResponsesResult, ResponsesStreamEvent, ChatCompletionRequest, ChatCompletionChoice, ChatCompletionResponse, ChatCompletionChunk, AIModel, AIModelListParams, AIModelListResponse, DocumentChunk, IndexDocumentRequest, IndexDocumentResponse, ConfigureAssistantRequest, ConfigureAssistantResponse, PublicChatRequest, PublicChatResponse, Session, RateLimitStatus, SessionStatistics, AiSession, AiSessionCreate, AiUsageReport, VoiceSessionRequest, VoiceSessionResponse, EphemeralTokenRequest, EphemeralTokenResponse, TranscriptionResponse, TTSRequest, GeneratePodcastRequest, PodcastScript, GeneratePodcastResponse, PodcastStatus, AIGenerateContentRequest, AIGenerateContentCandidate, AIGenerateContentResponse, AIGenerateImageRequest, AIGenerateImageResponse, AIGeneratedImage, AISearchPhotosRequest, AISearchPhotosPhoto, AISearchPhotosResponse, AIUploadedFile, AICacheRef, };
3
3
  declare namespace aiInternal {
4
4
  namespace chat {
@@ -34,6 +34,20 @@ declare namespace aiInternal {
34
34
  */
35
35
  function listTools(collectionId: string, query?: AgentToolsQuery): Promise<AgentToolsResponse>;
36
36
  }
37
+ namespace tools {
38
+ /**
39
+ * Invoke ONE built-in server tool directly — no model in the loop. This is the
40
+ * "direct code" caller of the orchestration-neutral tool registry: the SAME tools the
41
+ * agent loop and the Responses `server_tools` path run, but called as a plain, typed,
42
+ * deterministic API. A front end can use it two ways: (1) call a tool straight as an
43
+ * API (e.g. `pdf.render` / `pdf.extract` behind a PDF UX), or (2) drive its OWN agent
44
+ * loop and execute each model tool-call here. Capability-gated server-side to the
45
+ * caller's grants (same blast-radius rules as the agent loop).
46
+ * POST /admin/collection/:collectionId/ai/tools/:name/run
47
+ */
48
+ function run<K extends AiToolName>(collectionId: string, name: K, args: AiToolArgsMap[K]): Promise<ToolRunResult>;
49
+ function run<T = any>(collectionId: string, name: string, args?: Record<string, any>): Promise<ToolRunResult<T>>;
50
+ }
37
51
  namespace skills {
38
52
  /** List the skills apps can invoke (name, description, input/output schema). */
39
53
  function list(collectionId: string): Promise<SkillsListResponse>;
@@ -135,6 +149,14 @@ declare namespace aiInternal {
135
149
  * Chat with product assistant (RAG)
136
150
  */
137
151
  function chat(collectionId: string, request: PublicChatRequest): Promise<PublicChatResponse>;
152
+ /**
153
+ * Public agent loop — run the orchestration-neutral tool loop on the consumer surface. Exposes an
154
+ * app's PUBLIC server functions (`agent.tool:true`, `visibility:'public'`) to a consumer assistant;
155
+ * built-in tools are opt-in by explicit `server_tools[]` allowlist only. The caller runs as the
156
+ * signed-in consumer ('owner', send the authKit bearer) or anonymous ('public').
157
+ * POST /public/collection/:collectionId/ai/agent/run
158
+ */
159
+ function agentRun(collectionId: string, body: PublicAgentRunRequest): Promise<AgentRunResult>;
138
160
  /**
139
161
  * Get session history
140
162
  */
@@ -218,6 +240,23 @@ declare namespace aiInternal {
218
240
  * Create or warm a cache for AI (admin)
219
241
  */
220
242
  function createCache(collectionId: string, params: any): Promise<AICacheRef>;
243
+ /**
244
+ * Declare a client tool + its browser-side handler. The `declaration` (name/description/input) is
245
+ * sent to the model; the `handler` runs in the page when the model calls it — so it can touch the
246
+ * DOM, the user's session, local state, etc. Pair with `runWithClientTools`.
247
+ */
248
+ function defineClientTool(name: string, spec: {
249
+ description?: string;
250
+ input?: Record<string, any>;
251
+ }, handler: (args: Record<string, any>) => any | Promise<any>): ClientTool;
252
+ /**
253
+ * Run an agent conversation that can call CLIENT tools, resolving them in the browser automatically.
254
+ * Drives the suspend/resume loop: call the agent → if it suspends on a client tool
255
+ * (`status:'requires_action'`), run the matching handler(s), append their outputs, and resubmit —
256
+ * until a final answer. Built-ins + app functions ride along via `toolbelt`. Only declared tools run
257
+ * (a call for an unknown tool throws), and each runs with the user's own auth.
258
+ */
259
+ function runWithClientTools(collectionId: string, opts: RunWithClientToolsOptions): Promise<AgentRunResult>;
221
260
  }
222
261
  export declare const ai: {
223
262
  chat: {
@@ -251,6 +290,7 @@ export declare const ai: {
251
290
  };
252
291
  public: {
253
292
  chat: typeof aiInternal.publicClient.chat;
293
+ agentRun: typeof aiInternal.publicClient.agentRun;
254
294
  getSession: typeof aiInternal.publicClient.getSession;
255
295
  clearSession: typeof aiInternal.publicClient.clearSession;
256
296
  getRateLimit: typeof aiInternal.publicClient.getRateLimit;
@@ -265,6 +305,11 @@ export declare const ai: {
265
305
  run: typeof aiInternal.agent.run;
266
306
  listTools: typeof aiInternal.agent.listTools;
267
307
  };
308
+ tools: {
309
+ run: typeof aiInternal.tools.run;
310
+ };
311
+ defineClientTool: typeof aiInternal.defineClientTool;
312
+ runWithClientTools: typeof aiInternal.runWithClientTools;
268
313
  skills: {
269
314
  list: typeof aiInternal.skills.list;
270
315
  run: typeof aiInternal.skills.run;
package/dist/api/ai.js CHANGED
@@ -90,6 +90,17 @@ var aiInternal;
90
90
  agent.listTools = listTools;
91
91
  })(agent = aiInternal.agent || (aiInternal.agent = {}));
92
92
  // ============================================================================
93
+ // Tools (direct, deterministic single-tool invocation — no model loop)
94
+ // ============================================================================
95
+ let tools;
96
+ (function (tools) {
97
+ async function run(collectionId, name, args = {}) {
98
+ const path = `/admin/collection/${encodeURIComponent(collectionId)}/ai/tools/${encodeURIComponent(name)}/run`;
99
+ return post(path, args);
100
+ }
101
+ tools.run = run;
102
+ })(tools = aiInternal.tools || (aiInternal.tools = {}));
103
+ // ============================================================================
93
104
  // Skills + Catalog (app-facing discovery)
94
105
  // ============================================================================
95
106
  let skills;
@@ -290,6 +301,18 @@ var aiInternal;
290
301
  return post(path, request);
291
302
  }
292
303
  publicClient.chat = chat;
304
+ /**
305
+ * Public agent loop — run the orchestration-neutral tool loop on the consumer surface. Exposes an
306
+ * app's PUBLIC server functions (`agent.tool:true`, `visibility:'public'`) to a consumer assistant;
307
+ * built-in tools are opt-in by explicit `server_tools[]` allowlist only. The caller runs as the
308
+ * signed-in consumer ('owner', send the authKit bearer) or anonymous ('public').
309
+ * POST /public/collection/:collectionId/ai/agent/run
310
+ */
311
+ async function agentRun(collectionId, body) {
312
+ const path = `/public/collection/${encodeURIComponent(collectionId)}/ai/agent/run`;
313
+ return post(path, body);
314
+ }
315
+ publicClient.agentRun = agentRun;
293
316
  /**
294
317
  * Get session history
295
318
  */
@@ -472,6 +495,52 @@ var aiInternal;
472
495
  return post(path, params);
473
496
  }
474
497
  aiInternal.createCache = createCache;
498
+ // ============================================================================
499
+ // Client tools (kind c) — front-end tools the model calls, executed in the page
500
+ // ============================================================================
501
+ /**
502
+ * Declare a client tool + its browser-side handler. The `declaration` (name/description/input) is
503
+ * sent to the model; the `handler` runs in the page when the model calls it — so it can touch the
504
+ * DOM, the user's session, local state, etc. Pair with `runWithClientTools`.
505
+ */
506
+ function defineClientTool(name, spec, handler) {
507
+ const declaration = { name, description: spec === null || spec === void 0 ? void 0 : spec.description, input: spec === null || spec === void 0 ? void 0 : spec.input };
508
+ return { declaration, handler };
509
+ }
510
+ aiInternal.defineClientTool = defineClientTool;
511
+ /**
512
+ * Run an agent conversation that can call CLIENT tools, resolving them in the browser automatically.
513
+ * Drives the suspend/resume loop: call the agent → if it suspends on a client tool
514
+ * (`status:'requires_action'`), run the matching handler(s), append their outputs, and resubmit —
515
+ * until a final answer. Built-ins + app functions ride along via `toolbelt`. Only declared tools run
516
+ * (a call for an unknown tool throws), and each runs with the user's own auth.
517
+ */
518
+ async function runWithClientTools(collectionId, opts) {
519
+ const { tools, surface = 'admin', maxRounds = 8 } = opts;
520
+ const byName = new Map(tools.map((t) => [t.declaration.name, t]));
521
+ const clientTools = tools.map((t) => t.declaration);
522
+ const toolbelt = Object.assign(Object.assign({}, (opts.toolbelt || {})), { clientTools });
523
+ let input = opts.input;
524
+ for (let round = 0; round < maxRounds; round++) {
525
+ const body = { input, instructions: opts.instructions, model: opts.model, maxSteps: opts.maxSteps, toolbelt };
526
+ const res = surface === 'public'
527
+ ? await publicClient.agentRun(collectionId, body)
528
+ : await agent.run(collectionId, body);
529
+ if (!res || res.status !== 'requires_action')
530
+ return res;
531
+ const outputs = [];
532
+ for (const call of (res.client_tool_calls || [])) {
533
+ const tool = byName.get(call.name);
534
+ if (!tool)
535
+ throw new Error(`runWithClientTools: model called undeclared client tool "${call.name}"`);
536
+ const out = await tool.handler(call.args || {});
537
+ outputs.push({ type: 'function_call_output', call_id: call.callId, output: typeof out === 'string' ? out : JSON.stringify(out !== null && out !== void 0 ? out : null) });
538
+ }
539
+ input = [...(res.items || []), ...outputs];
540
+ }
541
+ throw new Error('runWithClientTools: exceeded maxRounds without a final answer');
542
+ }
543
+ aiInternal.runWithClientTools = runWithClientTools;
475
544
  })(aiInternal || (aiInternal = {}));
476
545
  export const ai = {
477
546
  chat: {
@@ -505,6 +574,7 @@ export const ai = {
505
574
  },
506
575
  public: {
507
576
  chat: aiInternal.publicClient.chat,
577
+ agentRun: aiInternal.publicClient.agentRun,
508
578
  getSession: aiInternal.publicClient.getSession,
509
579
  clearSession: aiInternal.publicClient.clearSession,
510
580
  getRateLimit: aiInternal.publicClient.getRateLimit,
@@ -519,6 +589,11 @@ export const ai = {
519
589
  run: aiInternal.agent.run,
520
590
  listTools: aiInternal.agent.listTools,
521
591
  },
592
+ tools: {
593
+ run: aiInternal.tools.run,
594
+ },
595
+ defineClientTool: aiInternal.defineClientTool,
596
+ runWithClientTools: aiInternal.runWithClientTools,
522
597
  skills: {
523
598
  list: aiInternal.skills.list,
524
599
  run: aiInternal.skills.run,
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 2.0.26 | Generated: 2026-09-28T11:34:04.010Z
3
+ Version: 2.0.28 | Generated: 2026-09-28T13:20:09.049Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -594,6 +594,8 @@ interface ResponsesRequest {
594
594
  only?: AiToolName[]
595
595
  exclude?: AiToolName[]
596
596
  maxSteps?: number
597
+ app_functions?: AgentAppFunctionsOption
598
+ toolbelt?: AgentToolbelt
597
599
  }
598
600
  ```
599
601
 
@@ -1177,6 +1179,96 @@ interface AgentRunRequest {
1177
1179
  allowCapabilities?: string[]
1178
1180
  only?: string[]
1179
1181
  exclude?: string[]
1182
+ appFunctions?: AgentAppFunctionsOption
1183
+ toolbelt?: AgentToolbelt
1184
+ }
1185
+ ```
1186
+
1187
+ **AgentAppFunctionsOption** (interface)
1188
+ ```typescript
1189
+ interface AgentAppFunctionsOption {
1190
+ appId: string
1191
+ channel?: string
1192
+ only?: string[]
1193
+ }
1194
+ ```
1195
+
1196
+ **AgentToolbelt** (interface)
1197
+ ```typescript
1198
+ interface AgentToolbelt {
1199
+ builtins?: boolean | AiToolName[]
1200
+ appFunctions?: AgentAppFunctionsOption | AgentAppFunctionsOption[]
1201
+ clientTools?: ClientToolDeclaration[]
1202
+ }
1203
+ ```
1204
+
1205
+ **ClientToolDeclaration** (interface)
1206
+ ```typescript
1207
+ interface ClientToolDeclaration {
1208
+ name: string
1209
+ description?: string
1210
+ input?: Record<string, any>
1211
+ }
1212
+ ```
1213
+
1214
+ **ClientToolCall** (interface)
1215
+ ```typescript
1216
+ interface ClientToolCall {
1217
+ callId: string
1218
+ name: string
1219
+ args: Record<string, any>
1220
+ }
1221
+ ```
1222
+
1223
+ **RequiresActionResult** (interface)
1224
+ ```typescript
1225
+ interface RequiresActionResult {
1226
+ status: 'requires_action'
1227
+ client_tool_calls: ClientToolCall[]
1228
+ items: any[]
1229
+ steps?: number
1230
+ toolResults?: AgentToolResult[]
1231
+ availableTools?: string[]
1232
+ }
1233
+ ```
1234
+
1235
+ **ClientTool** (interface)
1236
+ ```typescript
1237
+ interface ClientTool {
1238
+ declaration: ClientToolDeclaration
1239
+ handler: (args: Record<string, any>) => any | Promise<any>
1240
+ }
1241
+ ```
1242
+
1243
+ **RunWithClientToolsOptions** (interface)
1244
+ ```typescript
1245
+ interface RunWithClientToolsOptions {
1246
+ input: string
1247
+ tools: ClientTool[]
1248
+ surface?: 'admin' | 'public'
1249
+ instructions?: string
1250
+ model?: string
1251
+ maxSteps?: number
1252
+ toolbelt?: AgentToolbelt
1253
+ maxRounds?: number
1254
+ }
1255
+ ```
1256
+
1257
+ **PublicAgentRunRequest** (interface)
1258
+ ```typescript
1259
+ interface PublicAgentRunRequest {
1260
+ input?: string
1261
+ prompt?: string
1262
+ instructions?: string
1263
+ model?: string
1264
+ maxSteps?: number
1265
+ server_tools?: AiToolName[]
1266
+ only?: AiToolName[]
1267
+ exclude?: AiToolName[]
1268
+ allowCapabilities?: AiToolCapability[]
1269
+ appFunctions?: AgentAppFunctionsOption
1270
+ toolbelt?: AgentToolbelt
1271
+ userId?: string
1180
1272
  }
1181
1273
  ```
1182
1274
 
@@ -1189,6 +1281,15 @@ interface AgentToolResult {
1189
1281
  }
1190
1282
  ```
1191
1283
 
1284
+ **ToolRunResult<T = any>** (interface)
1285
+ ```typescript
1286
+ interface ToolRunResult<T = any> {
1287
+ name: string
1288
+ isError: boolean
1289
+ result: T
1290
+ }
1291
+ ```
1292
+
1192
1293
  **AgentRunResult** (interface)
1193
1294
  ```typescript
1194
1295
  interface AgentRunResult {
@@ -1367,6 +1468,27 @@ interface PdfMergeArgs {
1367
1468
  }
1368
1469
  ```
1369
1470
 
1471
+ **PdfInspectArgs** (interface)
1472
+ ```typescript
1473
+ interface PdfInspectArgs {
1474
+ url: string; maxPages?: number; minTextChars?: number
1475
+ }
1476
+ ```
1477
+
1478
+ **PdfRenderArgs** (interface)
1479
+ ```typescript
1480
+ interface PdfRenderArgs {
1481
+ url: string; page?: number; dpi?: number
1482
+ }
1483
+ ```
1484
+
1485
+ **PdfExtractArgs** (interface)
1486
+ ```typescript
1487
+ interface PdfExtractArgs {
1488
+ url: string; schema?: Record<string, any>; prompt?: string; maxPages?: number; dpi?: number
1489
+ }
1490
+ ```
1491
+
1370
1492
  **HttpRequestArgs** (interface)
1371
1493
  ```typescript
1372
1494
  interface HttpRequestArgs {
@@ -1399,6 +1521,9 @@ interface AiToolArgsMap {
1399
1521
  'pdf.create': PdfCreateArgs
1400
1522
  'pdf.fill': PdfFillArgs
1401
1523
  'pdf.merge': PdfMergeArgs
1524
+ 'pdf.inspect': PdfInspectArgs
1525
+ 'pdf.render': PdfRenderArgs
1526
+ 'pdf.extract': PdfExtractArgs
1402
1527
  'http.request': HttpRequestArgs
1403
1528
  'translate': TranslateArgs
1404
1529
  }
@@ -1467,6 +1592,34 @@ interface TranslateResult {
1467
1592
  }
1468
1593
  ```
1469
1594
 
1595
+ **PdfInspectPage** (interface)
1596
+ ```typescript
1597
+ interface PdfInspectPage {
1598
+ page: number; width: number; height: number; textChars: number; hasText: boolean; imageCount: number; likelyType: 'text-native' | 'text+raster' | 'raster-only' | 'curve-only'
1599
+ }
1600
+ ```
1601
+
1602
+ **PdfInspectResult** (interface)
1603
+ ```typescript
1604
+ interface PdfInspectResult {
1605
+ url: string; pageCount: number; inspectedPages: number; totalTextChars: number; isTextNative: boolean; isCurveOnly: boolean; recommendedPath: 'text' | 'vision'; note: string; pages: PdfInspectPage[]
1606
+ }
1607
+ ```
1608
+
1609
+ **PdfRenderResult** (interface)
1610
+ ```typescript
1611
+ interface PdfRenderResult {
1612
+ url: string | null; page: number; pageCount: number; dpi: number; width: number; height: number
1613
+ }
1614
+ ```
1615
+
1616
+ **PdfExtractResult** (interface)
1617
+ ```typescript
1618
+ interface PdfExtractResult {
1619
+ url: string; method: 'text' | 'vision'; pagesRead: number; fields: Record<string, any>
1620
+ }
1621
+ ```
1622
+
1470
1623
  **ResponsesAgentTrace** (interface)
1471
1624
  ```typescript
1472
1625
  interface ResponsesAgentTrace {
@@ -2135,6 +2288,18 @@ interface AppFunctionDef {
2135
2288
  dataScope?: 'collection' | 'global';
2136
2289
  apiVersion?: string;
2137
2290
  handler?: string;
2291
+ agent?: AppFunctionAgentExposure;
2292
+ }
2293
+ ```
2294
+
2295
+ **AppFunctionAgentExposure** (interface)
2296
+ ```typescript
2297
+ interface AppFunctionAgentExposure {
2298
+ tool: boolean;
2299
+ title?: string;
2300
+ description?: string;
2301
+ input?: Record<string, any>;
2302
+ approval?: 'auto' | 'require';
2138
2303
  }
2139
2304
  ```
2140
2305
 
@@ -11250,6 +11415,10 @@ Get the active transfer/status for a proof (owner, collection admin, or the name
11250
11415
  request: PublicChatRequest) → `Promise<PublicChatResponse>`
11251
11416
  Chat with product assistant (RAG)
11252
11417
 
11418
+ **agentRun**(collectionId: string,
11419
+ body: PublicAgentRunRequest) → `Promise<AgentRunResult>`
11420
+ Public agent loop — run the orchestration-neutral tool loop on the consumer surface. Exposes an app's PUBLIC server functions (`agent.tool:true`, `visibility:'public'`) to a consumer assistant; built-in tools are opt-in by explicit `server_tools[]` allowlist only. The caller runs as the signed-in consumer ('owner', send the authKit bearer) or anonymous ('public'). POST /public/collection/:collectionId/ai/agent/run
11421
+
11253
11422
  **getSession**(collectionId: string, sessionId: string) → `Promise<Session>`
11254
11423
  Get session history
11255
11424
 
@@ -11483,6 +11652,22 @@ Reverse lookup by ref via POST (public). `POST /public/collection/:collectionId/
11483
11652
  **renderSource**(collectionId: string,
11484
11653
  body: TemplateRenderSourceRequest) → `Promise<TemplateRenderSourceResponse>`
11485
11654
 
11655
+ ### tools
11656
+
11657
+ **run**(collectionId: string,
11658
+ name: K,
11659
+ args: AiToolArgsMap[K],) → `Promise<ToolRunResult>
11660
+ export async function run<T = any>(
11661
+ collectionId: string,
11662
+ name: string,
11663
+ args?: Record<string, any>,
11664
+ ): Promise<ToolRunResult<T>>
11665
+ export async function run(
11666
+ collectionId: string,
11667
+ name: string,
11668
+ args: Record<string, any> =`
11669
+ Invoke ONE built-in server tool directly — no model in the loop. This is the "direct code" caller of the orchestration-neutral tool registry: the SAME tools the agent loop and the Responses `server_tools` path run, but called as a plain, typed, deterministic API. A front end can use it two ways: (1) call a tool straight as an API (e.g. `pdf.render` / `pdf.extract` behind a PDF UX), or (2) drive its OWN agent loop and execute each model tool-call here. Capability-gated server-side to the caller's grants (same blast-radius rules as the agent loop). POST /admin/collection/:collectionId/ai/tools/:name/run
11670
+
11486
11671
  ### translations
11487
11672
 
11488
11673
  **hashText**(text: string, options?: TranslationHashOptions) → `Promise<string>`
@@ -69,6 +69,34 @@ The platform derives an **MCP tool list** from your manifest and drives the stan
69
69
  → `tools/call` → structured result — with streaming, cancellation, and approval layered on. You don't
70
70
  implement the wire protocol; you declare tools and write handlers. (The live loop is the staged part.)
71
71
 
72
+ ## Client tools (front-end)
73
+
74
+ For genuinely UI-coupled actions — read a field the user is editing, open a picker, update on-page
75
+ state — declare a **client tool**: the model calls it, but it runs in your page, not on the server.
76
+ Build each with `ai.defineClientTool` and drive the loop with `ai.runWithClientTools`, which resolves
77
+ every client call automatically (suspend → run handler → resubmit) until the assistant answers:
78
+
79
+ ```ts
80
+ const pickDate = SL.ai.defineClientTool(
81
+ 'ui.pickDate',
82
+ { description: 'Open the date picker and return the chosen ISO date.',
83
+ input: { type: 'object', properties: { min: { type: 'string' } } } },
84
+ async ({ min }) => ({ date: await openDatePicker({ min }) }) // runs in the browser
85
+ )
86
+
87
+ const result = await SL.ai.runWithClientTools(collectionId, {
88
+ input: 'Book me the earliest slot next week',
89
+ tools: [pickDate],
90
+ toolbelt: { builtins: ['web.search'], appFunctions: [{ appId: 'booking' }] }, // mix all three kinds
91
+ // surface: 'public' // for a consumer assistant
92
+ })
93
+ ```
94
+
95
+ The handler runs with the user's own session (auto-scoped), and only tools you declared can be called
96
+ (a call for anything else throws). A client-tool name can never collide with a built-in or a server
97
+ function — those always win. This is the third tool **kind**, alongside built-in tools and app
98
+ functions, all declared together in one `toolbelt`.
99
+
72
100
  ## Security
73
101
 
74
102
  Identical to server functions — nothing new to reason about:
@@ -271,6 +271,96 @@ name (a first-party builtin still wins). Always prefer an appId.
271
271
 
272
272
  ---
273
273
 
274
+ ## Exposing a function to the AI agent
275
+
276
+ An `http` function can be offered to the AI agent as a **callable tool**, alongside the built-in
277
+ tools. The model calls it, the server runs it, and the result is fed back into the loop. Opt in from
278
+ the manifest with an `agent` block (full reference: [agent-tools.md](agent-tools.md)):
279
+
280
+ ```jsonc
281
+ {
282
+ "name": "getLoyaltyBalance",
283
+ "trigger": { "type": "http" },
284
+ "visibility": "admin",
285
+ "authority": "caller",
286
+ "capabilities": ["sl:records:read"],
287
+ "agent": {
288
+ "tool": true,
289
+ "title": "Loyalty balance",
290
+ "description": "Look up a member's current loyalty points balance.",
291
+ "input": {
292
+ "type": "object",
293
+ "properties": { "memberId": { "type": "string" } },
294
+ "required": ["memberId"]
295
+ },
296
+ "approval": "auto" // 'require' = human confirms before each call
297
+ }
298
+ }
299
+ ```
300
+
301
+ The agent becomes **just another caller surface** — your function's `visibility`, `authority`, and
302
+ `capabilities` are enforced exactly as on the http route. Nothing new is granted. The agent's tool
303
+ arguments arrive as the function's request `body`, and whatever you return becomes the tool result the
304
+ model sees. `approval: "require"` tools are held back from the autonomous server-side loop until the
305
+ human-approval UX ships.
306
+
307
+ Include your app's functions in an agent run:
308
+
309
+ ```ts
310
+ // Agentic Responses:
311
+ await SL.ai.chat.responses.create(collectionId, {
312
+ model: 'balanced', input: 'What is member 42's balance?',
313
+ server_tools: true, // built-in tools
314
+ app_functions: { appId: 'my-loyalty-app' }, // + this app's ai.tool functions
315
+ })
316
+
317
+ // Or the one-shot agent loop:
318
+ await SL.ai.agent.run(collectionId, {
319
+ input: '…', appFunctions: { appId: 'my-loyalty-app' },
320
+ })
321
+ ```
322
+
323
+ On the **consumer surface**, a `visibility: "public"` function reaches a public assistant via the
324
+ public agent loop — the caller runs as the signed-in consumer (`'owner'`, send the authKit bearer) or
325
+ anonymous (`'public'`):
326
+
327
+ ```ts
328
+ await SL.ai.publicClient.agentRun(collectionId, {
329
+ input: '…',
330
+ appFunctions: { appId: 'my-app' }, // this app's public agent tools
331
+ server_tools: ['web.search'], // built-ins are an explicit allowlist on the public surface
332
+ })
333
+ ```
334
+
335
+ `channel` (default `'stable'`, pass `'dev'` to test a dev build) and `only: string[]` narrow which
336
+ functions are exposed. Only `http` functions with `agent.tool: true` are eligible; `event`/`cron`
337
+ functions never are. A built-in tool of the same name wins the clash.
338
+
339
+ ### One unified toolbelt
340
+
341
+ Rather than juggling `server_tools` + `app_functions`, declare everything in one `toolbelt` — built-in
342
+ tools, one **or several** apps' functions, and (reserved, staged) front-end client tools:
343
+
344
+ ```ts
345
+ await SL.ai.chat.responses.create(collectionId, {
346
+ input: '…',
347
+ toolbelt: {
348
+ builtins: ['web.search', 'document.read'], // true = all, [names] = subset, omit = none
349
+ appFunctions: [{ appId: 'loyalty' }, { appId: 'catalog' }], // several apps at once
350
+ // clientTools: [ … ] // reserved — the client-tool bridge is staged
351
+ },
352
+ })
353
+ ```
354
+
355
+ The same `toolbelt` works on `ai.agent.run` and `ai.publicClient.agentRun` (on the public surface
356
+ `builtins` is an explicit allowlist — `true` is treated as none). Precedence on a name clash: a
357
+ built-in wins, then earlier `appFunctions` sources win over later ones.
358
+
359
+ > Directly (no model): every function is also callable deterministically — `SL.functions.call` /
360
+ > `callAdmin` (above), the same way the built-in tools are callable via `SL.ai.tools.run`.
361
+
362
+ ---
363
+
274
364
  ## Runtime — what your function can use
275
365
 
276
366
  Your function runs in a **web-standard sandbox** (think Cloudflare Workers / Deno), **not