@squidcloud/cli 1.0.486 → 1.0.488
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/dist/index.js +1 -1
- package/dist/resources/claude/skills/squid-development/SKILL.md +8 -8
- package/dist/resources/claude/skills/squid-development/reference/admin.md +30 -0
- package/dist/resources/claude/skills/squid-development/reference/ai.md +390 -7
- package/dist/resources/claude/skills/squid-development/reference/api.md +46 -1
- package/dist/resources/claude/skills/squid-development/reference/backend.md +57 -1
- package/dist/resources/claude/skills/squid-development/reference/chat-widget.md +1 -1
- package/dist/resources/claude/skills/squid-development/reference/client.md +74 -3
- package/dist/resources/claude/skills/squid-development/reference/console.md +5 -0
- package/dist/resources/claude/skills/squid-development/reference/databases.md +48 -0
- package/dist/resources/claude/skills/squid-development/reference/security.md +74 -1
- package/dist/resources/claude/skills/squid-integrations/reference/connector-functions.md +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -40729,7 +40729,7 @@ module.exports = /*#__PURE__*/JSON.parse('{"name":"seek-bzip","version":"1.0.6",
|
|
|
40729
40729
|
(module) {
|
|
40730
40730
|
|
|
40731
40731
|
"use strict";
|
|
40732
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@squidcloud/cli","version":"1.0.
|
|
40732
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@squidcloud/cli","version":"1.0.488","description":"The Squid CLI","main":"dist/index.js","scripts":{"start":"node dist/index.js","start-ts":"ts-node -r tsconfig-paths/register src/index.ts","prebuild":"rimraf dist","build":"webpack --mode=production","build:dev":"webpack --mode=development","lint":"eslint","link":"npm run build && chmod 755 dist/index.js && npm link","watch":"webpack --watch","deploy":"npm run build && npm pack --silent | xargs -I {} mv {} package.tgz && npm install -g package.tgz && rm -rf package.tgz","publish:public":"npm run build && npm publish --access public"},"files":["dist/**/*"],"bin":{"squid":"dist/index.js"},"keywords":[],"author":"","license":"ISC","engines":{"node":">=18.0.0"},"dependencies":{"@squidcloud/local-backend":"^1.0.488","adm-zip":"^0.5.16","copy-webpack-plugin":"^14.0.0","decompress":"^4.2.1","logpipes":"^1.11.0","nodemon":"^3.1.9","terser-webpack-plugin":"^5.5.0","ts-loader":"^9.5.1","ts-node":"^10.9.2","tsconfig-paths":"^4.2.0","tsconfig-paths-webpack-plugin":"^4.1.0","webpack":"^5.106.2","zip-webpack-plugin":"^4.0.1"},"devDependencies":{"@types/adm-zip":"^0.5.7","@types/decompress":"^4.2.7","terminal-link":"^3.0.0"}}');
|
|
40733
40733
|
|
|
40734
40734
|
/***/ }
|
|
40735
40735
|
|
|
@@ -21,15 +21,15 @@ Squid is a backend-as-a-service platform that provides:
|
|
|
21
21
|
|
|
22
22
|
## Feature-Specific Guidance
|
|
23
23
|
|
|
24
|
-
- **[client.md](reference/client.md)** → client SDK, initialization, setup, auth, login, tokens, OAuth, appId, region, environmentId, apiKey, authProvider, getToken, setAuthProvider, Squid client, frontend, collection, executeFunction, executeFunctionWithHeaders, getWebhookUrl, externalAuth, saveAuthCode, getAccessToken, storage, uploadFile, downloadUrl, queues, produce, consume, distributed locks, acquireLock, withLock, web, aiSearch, getUrlContent, createShortUrl, jobs, getJob, awaitJob, observability, metrics, reportMetric, queryMetrics, notifications, publishNotification, observeNotifications
|
|
25
|
-
- **[console.md](reference/console.md)** → Squid Console, web UI, organizations, applications, AI Studio, knowledge bases, integrations, monitoring, logs, API keys, secrets, testing, debugging, profile settings, management API keys
|
|
26
|
-
- **[ai.md](reference/ai.md)** → AI agents, chat, ask, askWithAnnotations, askAsync, askWithVoiceResponse, transcribeAndChat, transcribeAndAsk, knowledge bases, RAG, embeddings, image generation, audio, transcription, text-to-speech, TTS, connectedAgents, connectedIntegrations, connectedKnowledgeBases, @aiFunction, @secureAiAgent, @secureAiQuery, memory, memoryOptions, voiceOptions, OpenAI, Anthropic, Gemini, Grok, DALL-E, Whisper, MCP, @mcpServer, @mcpTool, executeAiQuery, executeAiApiCall, extraction, createPdf, upsert agent, listAgents
|
|
24
|
+
- **[client.md](reference/client.md)** → client SDK, initialization, setup, auth, login, tokens, OAuth, appId, region, environmentId, apiKey, authProvider, getToken, setAuthProvider, Squid client, frontend, collection, executeFunction, executeFunctionWithHeaders, getWebhookUrl, externalAuth, saveAuthCode, getAccessToken, storage, uploadFile, downloadUrl, queues, produce, consume, distributed locks, acquireLock, withLock, web, aiSearch, allowedDomains, getUrlContent, createShortUrl, jobs, getJob, awaitJob, startJob, completeJob, failJob, events, squid.events(), emit, TriggerEvent, LangGraph, squid.langGraph(), invoke, invokeAsync, resume, getState, deleteThread, observability, metrics, reportMetric, queryMetrics, setMetricAnnotations, notifications, publishNotification, observeNotifications
|
|
25
|
+
- **[console.md](reference/console.md)** → Squid Console, web UI, organizations, applications, AI Studio, knowledge bases, knowledge graph toggle, integrations, monitoring, logs, API keys, secrets, testing, debugging, profile settings, management API keys
|
|
26
|
+
- **[ai.md](reference/ai.md)** → AI agents, chat, ask, askWithAnnotations, askAsync, askWithVoiceResponse, transcribeAndChat, transcribeAndAsk, knowledge bases, RAG, embeddings, image generation, audio, transcription, text-to-speech, TTS, connectedAgents, connectedIntegrations, connectedKnowledgeBases, @aiFunction, @secureAiAgent, @secureAiQuery, memory, memoryOptions, metricAnnotations, voiceOptions, OpenAI, Anthropic, Gemini, Grok, DALL-E, Whisper, MCP, @mcpServer, @mcpTool, agent as MCP server, updateMcpServer, generateMcpDescriptions, agent API key, executeAiQuery, executeAiApiCall, extraction, createPdf, upsert agent, listAgents, listChatModels, listFunctions, spreadsheet understanding, querySpreadsheetsWithAi, searchMode, keyword search, xlsx, csv, grep, literal scan, knowledge graph, GraphRAG, graphRag, graph search, queryGraph, getGraphStatus, rebuildGraph, exploreGraph, searchWithGraphContext, graphOptions, graphFilter, underConcept, entityTypes, queryKnowledgeGraph, bulk ingestion, bulkUpsertContexts, createBulkUploadUrls, observeBulkIngestionJob, listContextsPage, generateMetadataFieldDescriptions
|
|
27
27
|
- **[chat-widget.md](reference/chat-widget.md)** → AI chat widget, @squidcloud/react-chat-widget, squid-chat-widget, squid-chat-widget-with-fab-button, embeddable chat, custom API webhook, AI query mode, chain-of-thought, status updates, session management, agentContext, memoryOptions, squid-ai-custom-api-url, squid-ai-agent-chat-options, FAB button, theming, CSS variables, localization, RTL, menu items slots, suggested prompts, voice transcription, error formatting, authentication, squid-auth-provider, onChange events
|
|
28
|
-
- **[databases.md](reference/databases.md)** → collections, documents, queries, subscriptions, snapshots, insert, update, delete, CRUD, real-time, dereference, pagination, transactions, query operators, eq, neq, gt, gte, lt, lte, like, in, nin, arrayIncludesSome, arrayIncludesAll, sortBy, limit, join queries, OR queries, @trigger, native queries, SQL, MongoDB, Elasticsearch, incrementInPath, decrementInPath, watch changes, doc(), projectFields, field projection, __docId__, __id
|
|
29
|
-
- **[backend.md](reference/backend.md)** → SquidService, @executable, @webhook, @trigger, TriggerRequest, @scheduler, @limits, rate limiting, quotas, decorators, backend functions, WebhookRequest, CronExpression, cron, file handling, SquidFile, getUserAuth, isAuthenticated, assertIsAuthenticated, createWebhookResponse, this.squid, this.secrets, @clientConnectionStateHandler, @onQueueMessage, QueueMessageRequest, queue message handler, server-side queue consumer, CLI, squid init, squid start, squid deploy, squid build, project structure, multiple services, service architecture, squidInject, cross-service communication
|
|
30
|
-
- **[security.md](reference/security.md)** → security rules, @secureDatabase, @secureCollection, @secureTopic, @secureStorage, @secureApi, @secureNativeQuery, @secureAiQuery, @secureAiAgent, @secureDistributedLock, @secureGraphQL, QueryContext, MutationContext, isSubqueryOf, affectsPath, permissions, authorization, row-level security, role-based access
|
|
31
|
-
- **[admin.md](reference/admin.md)** → ManagementClient, management API keys, organizations, applications, programmatic management, CI/CD, automation, integrations admin, secrets admin, upsertIntegration, discoverDataConnectionSchema, testDataConnection, createOrganization, createApplication
|
|
32
|
-
- **[api.md](reference/api.md)** → API, REST API, HTTP endpoints, API reference, Agent API, AI Audio API, AI Image API, KnowledgeBase API, Matchmaking API, Web Utilities API, Database API, Extraction API
|
|
28
|
+
- **[databases.md](reference/databases.md)** → collections, documents, queries, subscriptions, snapshots, insert, update, delete, CRUD, real-time, dereference, pagination, transactions, query operators, eq, neq, gt, gte, lt, lte, like, in, nin, arrayIncludesSome, arrayIncludesAll, sortBy, limit, join queries, OR queries, @trigger, native queries, SQL, MongoDB, Elasticsearch, incrementInPath, decrementInPath, watch changes, doc(), docId, docIds, query.delete(), delete by query, projectFields, field projection, __docId__, __id
|
|
29
|
+
- **[backend.md](reference/backend.md)** → SquidService, @executable, @webhook, @trigger, TriggerRequest, @scheduler, @limits, rate limiting, quotas, decorators, backend functions, WebhookRequest, CronExpression, cron, file handling, SquidFile, getUserAuth, isAuthenticated, assertIsAuthenticated, createWebhookResponse, this.squid, getSquid, getPassiveSquid, this.secrets, @clientConnectionStateHandler, @onQueueMessage, QueueMessageRequest, queue message handler, server-side queue consumer, @eventHandler, event handler, workspace, CLI, squid init, squid start, squid deploy, squid build, squid kb-upload, printBundle, project structure, multiple services, service architecture, squidInject, cross-service communication
|
|
30
|
+
- **[security.md](reference/security.md)** → security rules, @secureDatabase, @secureCollection, @publicCollection, public read, @secureTopic, @secureStorage, @secureApi, @secureNativeQuery, @secureAiQuery, @secureAiAgent, @secureLangGraph, @secureDistributedLock, @secureGraphQL, QueryContext, MutationContext, SecureLangGraphContext, isSubqueryOf, affectsPath, permissions, authorization, row-level security, role-based access
|
|
31
|
+
- **[admin.md](reference/admin.md)** → ManagementClient, management API keys, organizations, applications, programmatic management, CI/CD, automation, integrations admin, secrets admin, connectors admin, squid.admin().connectors(), attach connector, appConnectors, upsertIntegration, discoverDataConnectionSchema, testDataConnection, createOrganization, createApplication
|
|
32
|
+
- **[api.md](reference/api.md)** → API, REST API, HTTP endpoints, API reference, Agent API, AiSettings API, AiFunction API, AI Audio API, AI Image API, KnowledgeBase API, Matchmaking API, Web Utilities API, Database API, Extraction API, apiKeyAuth, agentApiKeyAuth, x-squid-agent-api-key, memoryOptions, memoryId, memoryMode
|
|
33
33
|
- **[openai.md](reference/openai.md)** → OpenAI, code interpreter, verbosity, reasoning models, o1, o3, gpt-5, DALL-E, Whisper, TTS, voice options, structured output, file upload
|
|
34
34
|
- **[connectors.md](reference/connectors.md)** → connectors, integrations, IntegrationType, postgres, mongo, auth0, s3, kafka, salesforce, essentials connector, built-in connector, @squidcloud/slack-client, @squidcloud/jira-client, @squidcloud/github-client, @squidcloud/salesforce-client, @squidcloud/freshdesk-client, @squidcloud/linear-client, @squidcloud/mail-client, @squidcloud/google-calendar-client, @squidcloud/google-drive-client, @squidcloud/teams-client, @squidcloud/sharepoint-client, @squidcloud/servicenow_csm-client, @squidcloud/jira-jsm-client, SquidSlackClient, SquidJiraClient, SquidGitHubClient, sendMail, sendMessage, searchIssues, createIssue, connector SDK
|
|
35
35
|
- **Integrations** → See `squid-integrations` skill for full catalog, connector AI functions, SDK methods, and configuration
|
|
@@ -5,6 +5,7 @@ This document covers programmatic management of Squid organizations, application
|
|
|
5
5
|
## Contents
|
|
6
6
|
- ManagementClient
|
|
7
7
|
- Admin Integrations
|
|
8
|
+
- Admin Connectors
|
|
8
9
|
- Admin Secrets
|
|
9
10
|
|
|
10
11
|
## ManagementClient
|
|
@@ -204,6 +205,35 @@ const openApiSchema = await integrations.discoverOpenApiSchema('my-api', {
|
|
|
204
205
|
const fileSchema = await integrations.discoverOpenApiSchemaFromFile('my-api');
|
|
205
206
|
```
|
|
206
207
|
|
|
208
|
+
### Admin - Connectors
|
|
209
|
+
|
|
210
|
+
Attach and detach **app-level (integration-less) connectors**. Unlike integrations, these carry no
|
|
211
|
+
per-connector configuration record — attaching one adds it to the application's `appConnectors` list
|
|
212
|
+
and (re)deploys its bundle. Any configuration the connector needs comes in out of band, e.g. via
|
|
213
|
+
application secrets.
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
const connectors = squid.admin().connectors();
|
|
217
|
+
|
|
218
|
+
// List the connectors explicitly attached to the app
|
|
219
|
+
const attached = await connectors.list(); // Array<ConnectorId>
|
|
220
|
+
|
|
221
|
+
// Attach (idempotent - attaching an already-attached connector is a no-op)
|
|
222
|
+
await connectors.connect('slack');
|
|
223
|
+
|
|
224
|
+
// Detach
|
|
225
|
+
await connectors.disconnect('slack');
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`ConnectorId` is one of: `bitbucket`, `confluence`, `cotomi`, `essentials`, `freshdesk`, `github`,
|
|
229
|
+
`google_calendar`, `google_drive`, `hubspot`, `sharepoint`, `jira`, `jira_jsm`, `linear`, `mail`,
|
|
230
|
+
`onedrive`, `outlook`, `salesforce`, `servicenow_csm`, `slack`, `teams`, `ticketmind_companion`,
|
|
231
|
+
`zendesk`.
|
|
232
|
+
|
|
233
|
+
**Connectors vs integrations:** use `squid.admin().integrations()` when the connection needs
|
|
234
|
+
configuration (credentials, a connection string, an OAuth app). Use `connectors()` for connectors the
|
|
235
|
+
app just needs turned on.
|
|
236
|
+
|
|
207
237
|
### Admin - Secrets
|
|
208
238
|
|
|
209
239
|
```typescript
|
|
@@ -54,6 +54,21 @@ import {
|
|
|
54
54
|
} from '@squidcloud/client';
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
**Active chat models** (the constants above hold active models only; deprecated ones carry a
|
|
58
|
+
`replacedBy` and are excluded unless asked for):
|
|
59
|
+
|
|
60
|
+
| Provider | `*_CHAT_MODEL_NAMES` |
|
|
61
|
+
|---|---|
|
|
62
|
+
| OpenAI | `gpt-5.4-mini`, `gpt-5.4-nano`, `gpt-5.5`, `gpt-5.5-pro`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna` |
|
|
63
|
+
| Anthropic | `claude-fable-5`, `claude-haiku-4-5-20251001`, `claude-opus-5`, `claude-sonnet-5` |
|
|
64
|
+
| Gemini | `gemini-3.1-pro`, `gemini-3.6-flash`, `gemini-3.5-flash-lite` |
|
|
65
|
+
| Grok | `grok-4.5`, `grok-4-1-fast-reasoning`, `grok-4-1-fast-non-reasoning` |
|
|
66
|
+
|
|
67
|
+
This list moves. **Don't hardcode a model name from this table into user code** — call
|
|
68
|
+
`squid.ai().listChatModels()`, which also returns the app's custom integration models
|
|
69
|
+
(`openai_compatible`, Bedrock, Vertex) and flags deprecated ones. See
|
|
70
|
+
[Application Settings](#application-settings).
|
|
71
|
+
|
|
57
72
|
**Model Categories:**
|
|
58
73
|
- **Chat Models**: Used for AI Agents, AI Query, etc. (OpenAI, Anthropic, Gemini, Grok)
|
|
59
74
|
- **Embedding Models**: Used for Knowledge Bases (OpenAI, Voyage)
|
|
@@ -76,6 +91,7 @@ An AI Agent can:
|
|
|
76
91
|
- Collaborate with other AI agents
|
|
77
92
|
- Process voice input and generate voice output
|
|
78
93
|
- Accept files as part of chat requests
|
|
94
|
+
- Refuse prompts that contain PII before they reach the model (see Rejecting PII in prompts)
|
|
79
95
|
|
|
80
96
|
### Creating and Managing Agents
|
|
81
97
|
|
|
@@ -99,7 +115,7 @@ await myAgent.upsert({
|
|
|
99
115
|
isPublic: false, // Whether the agent is publicly accessible
|
|
100
116
|
auditLog: true, // Enable audit logging for compliance
|
|
101
117
|
options: {
|
|
102
|
-
model: 'gpt-5-mini', // or 'claude-sonnet-
|
|
118
|
+
model: 'gpt-5.4-mini', // or 'claude-sonnet-5', 'gemini-3.6-flash' - see Supported Models
|
|
103
119
|
instructions: 'You are a helpful customer support assistant. Be concise and professional.',
|
|
104
120
|
temperature: 0.7
|
|
105
121
|
}
|
|
@@ -111,7 +127,7 @@ console.log(agentInfo.id, agentInfo.description, agentInfo.options.model);
|
|
|
111
127
|
|
|
112
128
|
// Update specific properties
|
|
113
129
|
await myAgent.updateInstructions('You are a technical support specialist.');
|
|
114
|
-
await myAgent.updateModel('claude-sonnet-
|
|
130
|
+
await myAgent.updateModel('claude-sonnet-5');
|
|
115
131
|
await myAgent.updateGuardrails(['no-harmful-content']);
|
|
116
132
|
|
|
117
133
|
// Delete an agent
|
|
@@ -140,8 +156,85 @@ await myAgent.updateConnectedAgents([
|
|
|
140
156
|
// Update or delete custom guardrails
|
|
141
157
|
await myAgent.updateCustomGuardrails('Never reveal sensitive information');
|
|
142
158
|
await myAgent.deleteCustomGuardrail();
|
|
159
|
+
|
|
160
|
+
// Refuse prompts that contain PII (see "Rejecting PII in prompts" below)
|
|
161
|
+
await myAgent.updatePii({ onDetect: 'reject' });
|
|
162
|
+
|
|
163
|
+
// Expose the agent itself as an MCP server (see "Agent as an MCP server" below)
|
|
164
|
+
await myAgent.updateMcpServer({ enabled: true, requireApiKey: true });
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Agent as an MCP Server
|
|
168
|
+
|
|
169
|
+
Any agent can be published as an MCP server at `/mcp/<agentId>` with **no backend code** — the server
|
|
170
|
+
exposes a single `ask` tool that calls the agent. This is the inverse of `@mcpServer`, which exposes
|
|
171
|
+
*your* backend tools to agents. Transport is the official MCP **Streamable HTTP** transport.
|
|
172
|
+
`updateMcpServer` requires an API key.
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
// AI-generate the server + tool descriptions from the agent's instructions and connected resources.
|
|
176
|
+
// Returns them WITHOUT persisting - save them with updateMcpServer.
|
|
177
|
+
const generated = await myAgent.generateMcpDescriptions();
|
|
178
|
+
|
|
179
|
+
await myAgent.updateMcpServer({
|
|
180
|
+
enabled: true,
|
|
181
|
+
description: generated.description, // served as `instructions` in the MCP initialize result
|
|
182
|
+
toolDescription: generated.toolDescription, // description of the `ask` tool in the tools manifest
|
|
183
|
+
requireApiKey: true, // require the agent's API key as a bearer token
|
|
184
|
+
oauthIntegrationId: 'auth0' // or OAuth-protect it with an auth integration
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**`AiAgentMcpServerConfig`:**
|
|
189
|
+
- `enabled` — whether the agent is exposed as an MCP server.
|
|
190
|
+
- `description` — defaults to the agent description when omitted.
|
|
191
|
+
- `toolDescription` — defaults to a generic ask-the-agent description.
|
|
192
|
+
- `requireApiKey` — when true, requests must present the agent's API key as
|
|
193
|
+
`Authorization: Bearer <agent API key>` or the `x-squid-agent-api-key` header.
|
|
194
|
+
- `oauthIntegrationId` — OAuth-protects the endpoint with an auth integration's bearer tokens.
|
|
195
|
+
- Combining `oauthIntegrationId` and `requireApiKey` accepts **either** credential.
|
|
196
|
+
- **With neither set, the MCP endpoint is public.**
|
|
197
|
+
|
|
198
|
+
### Rejecting PII in prompts
|
|
199
|
+
|
|
200
|
+
`pii` refuses an incoming prompt that carries personal information **before it reaches the
|
|
201
|
+
agent's model**. The prompt is not answered, no quota is consumed, and nothing is written to
|
|
202
|
+
chat memory. The caller gets an error whose message starts with `PII_DETECTED_IN_PROMPT`.
|
|
203
|
+
|
|
204
|
+
Do not confuse it with `guardrails.disablePii`, which is the opposite direction — that one asks
|
|
205
|
+
the agent's own model not to *emit* PII in its answer, and is best-effort prompt text. Use `pii`
|
|
206
|
+
when the requirement is that the data must not reach the model at all.
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
await squid.ai().agent('support-agent').updatePii({
|
|
210
|
+
onDetect: 'reject', // 'off' (default) disables screening
|
|
211
|
+
entities: ['email', 'ssn'], // omit for every kind
|
|
212
|
+
customRules: ['internal case numbers like CASE-12345'],
|
|
213
|
+
allowList: ['support@squid.cloud'], // literal values that never count as PII
|
|
214
|
+
classifierModel: 'gpt-5.6-luna', // default; small and fast
|
|
215
|
+
});
|
|
143
216
|
```
|
|
144
217
|
|
|
218
|
+
| Field | Meaning |
|
|
219
|
+
|---|---|
|
|
220
|
+
| `onDetect` | `'reject'` refuses the prompt; `'off'` (default) disables screening |
|
|
221
|
+
| `entities` | Kinds to screen for: `email`, `phoneNumber`, `creditCard`, `ssn`, `iban`, `passport`. Omitted means all |
|
|
222
|
+
| `customRules` | App-specific PII in plain language — anything a model can recognize from a description |
|
|
223
|
+
| `allowList` | Exact values that must never be flagged |
|
|
224
|
+
| `classifierModel` | Model doing the screening. Defaults to `gpt-5.6-luna` |
|
|
225
|
+
|
|
226
|
+
Things to know before enabling it:
|
|
227
|
+
|
|
228
|
+
- **Every prompt costs one extra model call.** Screening runs ahead of the agent's own model, so
|
|
229
|
+
it adds latency and tokens to each turn.
|
|
230
|
+
- **It is prompt-side only.** Tool results and knowledge-base chunks can still bring PII into the
|
|
231
|
+
model. Don't describe it to a user as an end-to-end guarantee.
|
|
232
|
+
- **It is fail-closed.** If the classifier is unreachable the turn is rejected with
|
|
233
|
+
`PII_SCREENING_UNAVAILABLE` rather than admitted unscreened.
|
|
234
|
+
- **It cannot be overridden per request.** Passing `pii` in `ask`/`chat` options is ignored; the
|
|
235
|
+
stored agent's value always wins, so a caller cannot opt out of its own screening.
|
|
236
|
+
- **Rejections are audited with the prompt redacted** — matched kinds are recorded, never values.
|
|
237
|
+
|
|
145
238
|
### Agent Lifecycle
|
|
146
239
|
|
|
147
240
|
1. **Creation** - Use `upsert()` to create a new agent with an ID
|
|
@@ -165,6 +258,8 @@ Agents become powerful when connected to resources:
|
|
|
165
258
|
- **Backend functions** (`functions`) - Custom logic via `@aiFunction` decorators
|
|
166
259
|
- **Integrations** (`connectedIntegrations`) - Database/API queries
|
|
167
260
|
- **Knowledge bases** (`connectedKnowledgeBases`) - RAG (Retrieval Augmented Generation)
|
|
261
|
+
- **Source repositories** (a `github`/`bitbucket` integration with code analysis enabled) - see
|
|
262
|
+
[Source-Code Analysis](#source-code-analysis)
|
|
168
263
|
|
|
169
264
|
#### Connected Agents
|
|
170
265
|
|
|
@@ -258,6 +353,8 @@ const response = await agent.ask('What is our return policy?', {
|
|
|
258
353
|
});
|
|
259
354
|
```
|
|
260
355
|
|
|
356
|
+
Knowledge bases with a knowledge graph give the agent graph retrieval and a `queryKnowledgeGraph` tool automatically — see [Knowledge Graph (GraphRAG)](#knowledge-graph-graphrag).
|
|
357
|
+
|
|
261
358
|
### Agent Chat Methods
|
|
262
359
|
|
|
263
360
|
```typescript
|
|
@@ -268,13 +365,20 @@ const agent = squid.ai().agent('my-agent');
|
|
|
268
365
|
// - NO connected resources: streams token-by-token
|
|
269
366
|
// - HAS connected resources: emits ONCE with complete response
|
|
270
367
|
const chatObs = agent.chat('What is your return policy?', {
|
|
271
|
-
// Memory management
|
|
368
|
+
// Memory management. `memoryOptions` is the only way to name a conversation and control
|
|
369
|
+
// history: `memoryId` names it, `memoryMode` controls whether history is read and written.
|
|
272
370
|
memoryOptions: {
|
|
273
371
|
memoryMode: 'read-write', // 'none' | 'read-only' | 'read-write'
|
|
274
372
|
memoryId: 'user-123', // Unique per user/session
|
|
275
373
|
expirationMinutes: 1440 // 24 hours
|
|
276
374
|
},
|
|
277
375
|
|
|
376
|
+
// Usage tracking: reported as `annotation.<key>` tags on Squid AI usage metrics, so token
|
|
377
|
+
// usage can be filtered/grouped by them. Inherited by nested connected-agent calls.
|
|
378
|
+
// Limits: max 10 entries, keys <= 64 chars, values <= 256 chars; excess is dropped/truncated.
|
|
379
|
+
// Set app-wide defaults with squid.setMetricAnnotations({...}); per-call values merge on top.
|
|
380
|
+
metricAnnotations: { feature: 'support-bot', requestSource: 'mobile' },
|
|
381
|
+
|
|
278
382
|
// Connected resources (can also be set on agent.upsert)
|
|
279
383
|
connectedAgents: [{ agentId: 'specialist-agent', description: 'Handles X' }],
|
|
280
384
|
functions: ['function1', 'function2'],
|
|
@@ -282,7 +386,7 @@ const chatObs = agent.chat('What is your return policy?', {
|
|
|
282
386
|
connectedKnowledgeBases: [{ knowledgeBaseId: 'kb1', description: 'When to use this KB' }],
|
|
283
387
|
|
|
284
388
|
// Model & generation
|
|
285
|
-
model: 'gpt-5-mini', // Override agent's default model
|
|
389
|
+
model: 'gpt-5.4-mini', // Override agent's default model
|
|
286
390
|
temperature: 0.7,
|
|
287
391
|
maxTokens: 4000,
|
|
288
392
|
maxOutputTokens: 2000,
|
|
@@ -305,6 +409,15 @@ const chatObs = agent.chat('What is your return policy?', {
|
|
|
305
409
|
maxAiCallStackSize: 5
|
|
306
410
|
},
|
|
307
411
|
|
|
412
|
+
// Refuse incoming prompts that carry PII (stored on the agent; ignored if passed per request)
|
|
413
|
+
pii: {
|
|
414
|
+
onDetect: 'reject',
|
|
415
|
+
entities: ['email', 'phoneNumber', 'creditCard', 'ssn', 'iban', 'passport'],
|
|
416
|
+
customRules: ['internal case numbers like CASE-12345'],
|
|
417
|
+
allowList: ['support@squid.cloud'],
|
|
418
|
+
classifierModel: 'gpt-5.6-luna'
|
|
419
|
+
},
|
|
420
|
+
|
|
308
421
|
// Files & voice
|
|
309
422
|
fileUrls: [
|
|
310
423
|
{ id: 'file1', type: 'image', purpose: 'context', url: 'https://...', description: 'Product image' }
|
|
@@ -320,7 +433,7 @@ const chatObs = agent.chat('What is your return policy?', {
|
|
|
320
433
|
useCodeInterpreter: 'llm', // 'none' | 'llm' (OpenAI/Gemini only)
|
|
321
434
|
executionPlanOptions: {
|
|
322
435
|
enabled: true,
|
|
323
|
-
model: 'gpt-5-mini',
|
|
436
|
+
model: 'gpt-5.4-mini',
|
|
324
437
|
reasoningEffort: 'high',
|
|
325
438
|
allowClarificationQuestions: false
|
|
326
439
|
},
|
|
@@ -436,6 +549,19 @@ const searchResults = await kb.search({
|
|
|
436
549
|
limit: 10
|
|
437
550
|
});
|
|
438
551
|
|
|
552
|
+
// Keyword (lexical, embedding-free) search — best for exact tokens: IDs, error codes, SKUs, file names.
|
|
553
|
+
// searchMode: 'vector' | 'hybrid' | 'keyword' | 'graph'. Default: 'hybrid' ('graph' on GraphRAG-enabled
|
|
554
|
+
// KBs; setting 'graph' on other KBs throws). Keyword mechanics differ by the KB's vectorDbType:
|
|
555
|
+
// mongoAtlas = ranked BM25, term-optional (partial matches still rank); postgres = unranked boolean
|
|
556
|
+
// filter where EVERY whitespace-separated term must appear as a literal substring — a multi-word
|
|
557
|
+
// prompt returns zero chunks when any one term is absent, so pass few, specific tokens.
|
|
558
|
+
// On postgres 'hybrid' degrades to dense-only (no lexical channel; hybridWeights is ignored off
|
|
559
|
+
// Mongo), so exact-token retrieval there REQUIRES explicitly setting searchMode: 'keyword'.
|
|
560
|
+
const keywordResults = await kb.search({
|
|
561
|
+
prompt: 'ERR_0000_4F2A',
|
|
562
|
+
searchMode: 'keyword'
|
|
563
|
+
});
|
|
564
|
+
|
|
439
565
|
// Get context
|
|
440
566
|
const context = await kb.getContext('doc-123');
|
|
441
567
|
|
|
@@ -443,14 +569,229 @@ const context = await kb.getContext('doc-123');
|
|
|
443
569
|
const allContexts = await kb.listContexts(1000); // truncateTextAfter
|
|
444
570
|
const contextIds = await kb.listContextIds();
|
|
445
571
|
|
|
572
|
+
// List a PAGE of contexts (prefer this over listContexts for large KBs)
|
|
573
|
+
const page = await kb.listContextsPage({
|
|
574
|
+
offset: 0,
|
|
575
|
+
limit: 50,
|
|
576
|
+
truncateTextAfter: 500,
|
|
577
|
+
search: 'invoice' // case-insensitive substring match on id/title only
|
|
578
|
+
});
|
|
579
|
+
|
|
446
580
|
// Download context
|
|
447
581
|
const download = await kb.downloadContext('doc-123');
|
|
448
582
|
|
|
449
583
|
// Delete contexts
|
|
450
584
|
await kb.deleteContext('doc-123');
|
|
451
585
|
await kb.deleteContexts(['doc-1', 'doc-2']);
|
|
586
|
+
|
|
587
|
+
// AI-generate descriptions for the KB's metadata fields (helps metadata filtering quality).
|
|
588
|
+
// Returns { fields } WITHOUT persisting - save them yourself via upsertKnowledgeBase.
|
|
589
|
+
// By default only fields with an empty description are generated.
|
|
590
|
+
const { fields } = await kb.generateMetadataFieldDescriptions({
|
|
591
|
+
fieldNames: ['category'], // optional; omitted => all declared fields
|
|
592
|
+
overwriteExisting: false // true regenerates fields that already have a description
|
|
593
|
+
});
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
`upsertContexts` accepts an options object with an `onUploaded` callback, fired once the payload has
|
|
597
|
+
been uploaded and accepted — server-side extraction and embedding continue after it, and the promise
|
|
598
|
+
still resolves only when ingestion completes:
|
|
599
|
+
|
|
600
|
+
```typescript
|
|
601
|
+
await kb.upsertContexts(contexts, files, { onUploaded: () => setStatus('Uploaded, indexing…') });
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
### Metadata Filters
|
|
605
|
+
|
|
606
|
+
A MongoDB-style grammar shared across KB context filtering, metrics tag filtering, and matchmaking
|
|
607
|
+
(the types live in `@squidcloud/client`). Used by `kb.search`, `kb.grep`, and the chat option
|
|
608
|
+
`contextMetadataFilterForKnowledgeBase`.
|
|
609
|
+
|
|
610
|
+
```typescript
|
|
611
|
+
// A bare scalar is shorthand for $eq
|
|
612
|
+
{ category: 'user-guide' }
|
|
613
|
+
|
|
614
|
+
// Field operators: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $exists
|
|
615
|
+
{ version: { $gte: 2 }, status: { $in: ['published', 'review'] }, archivedAt: { $exists: false } }
|
|
616
|
+
|
|
617
|
+
// Boolean combinators
|
|
618
|
+
{ $or: [{ category: 'faq' }, { $and: [{ category: 'guide' }, { version: { $gte: 2 } }] }] }
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
**`$underPath` (knowledge bases only)** — segment-safe subtree match on a hierarchical string field
|
|
622
|
+
such as `folderPath`:
|
|
623
|
+
|
|
624
|
+
```typescript
|
|
625
|
+
{ folderPath: { $underPath: 'reports/2023' } }
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
Matches `reports/2023` and `reports/2023/q1/deck`, but never the lexical sibling
|
|
629
|
+
`reports/2023 drafts` — the `/` separator supplies the segment boundary, so subtree scoping cannot
|
|
630
|
+
leak into a sibling that merely shares a prefix. Leading/trailing `/` in the operand are stripped, an
|
|
631
|
+
empty operand matches every document that *has* the field, and matching is **case-sensitive**.
|
|
632
|
+
|
|
633
|
+
The canonical `folderPath` form: POSIX `/` separators, no leading/trailing slash, `''` (not
|
|
634
|
+
`undefined`) for the upload root, case preserved, NFC-normalized. `folderPath` is an ordinary
|
|
635
|
+
metadata key, not a reserved one.
|
|
636
|
+
|
|
637
|
+
`$underPath` is deliberately absent from the shared filter type that metrics and matchmaking consume
|
|
638
|
+
— they reject unknown operators at runtime.
|
|
639
|
+
|
|
640
|
+
### Literal Scan (grep)
|
|
641
|
+
|
|
642
|
+
`grep()` scans the KB's **raw extracted text** (pre-chunking, so spreadsheet rows appear verbatim)
|
|
643
|
+
for a literal string and returns every matching line with its file and locator. Use it when you need
|
|
644
|
+
characters, not meaning: IDs, error codes, SKUs, exact phrases.
|
|
645
|
+
|
|
646
|
+
```typescript
|
|
647
|
+
const hits = await kb.grep('ERR_0000_4F2A', {
|
|
648
|
+
metadataFilter: { category: 'runbooks' }, // same grammar as chunk search, incl. $and/$or/$underPath
|
|
649
|
+
maxMatches: 100
|
|
650
|
+
});
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
- The pattern is **always literal** — every regex metacharacter is escaped — and may span lines.
|
|
654
|
+
- Matching is case-insensitive **for ASCII letters only**: `acme` finds `ACME`, but `café` does not
|
|
655
|
+
find `CAFÉ`. Search the exact casing for non-ASCII text.
|
|
656
|
+
- `metadataFilter` selects contexts **before** any text is matched.
|
|
657
|
+
|
|
658
|
+
### Knowledge Graph (GraphRAG)
|
|
659
|
+
|
|
660
|
+
A graph-enabled knowledge base extracts an entity/relationship graph at ingest and layers concepts on top (document nodes, themes clustered from the entity graph, facet trees from context metadata). Retrieval can then follow links across documents — answering questions whose supporting facts are split over several files — and agents can navigate corpus structure instead of only quoting passages.
|
|
661
|
+
|
|
662
|
+
Requirements: `vectorDbType: 'mongoAtlas'` (immutable after creation — a `postgres` KB can never gain a graph). `graphRag` itself is mutable: enabling it on an existing Atlas KB auto-backfills the graph; disabling only makes graph search unavailable and RETAINS the extracted graph data, so re-enabling is cheap (a structural build folds in never-indexed contexts, reusing prior extraction). To actually reclaim the graph data, call `rebuildGraph()` while `graphRag.enabled` is false — that is the explicit cleanup path; otherwise only deleting the KB drops it. Extraction runs an LLM over every chunk at ingest — budget before enabling on a large corpus, and track spend via `getGraphStatus()`.
|
|
663
|
+
|
|
664
|
+
```typescript
|
|
665
|
+
// Enable at creation, or later by upserting with the current config spread — upserting
|
|
666
|
+
// graphRag replaces the whole object, so omitted fields are lost.
|
|
667
|
+
await kb.upsertKnowledgeBase({
|
|
668
|
+
description: 'Company filings',
|
|
669
|
+
vectorDbType: 'mongoAtlas', // Required for the graph.
|
|
670
|
+
graphRag: {
|
|
671
|
+
enabled: true,
|
|
672
|
+
// Optional extraction hint; proper nouns (orgs, people, places) extract far more
|
|
673
|
+
// reliably than generic product categories.
|
|
674
|
+
entityTypes: ['ORGANIZATION', 'PRODUCT', 'LOCATION'],
|
|
675
|
+
// Other fields: extractionModel, autoBuildDebounceMs (quiet window before automatic
|
|
676
|
+
// concept builds, default 5 min), concepts: { facets: 'auto' | [fields], pathFacets:
|
|
677
|
+
// [{ field: 'folderPath', type: 'path' }] } (folder tree from a path metadata field).
|
|
678
|
+
},
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
// Graph search: seeds entities from the prompt via vector search, walks relationships,
|
|
682
|
+
// maps reached entities back to chunks, fuses with hybrid search. 'graph' is the DEFAULT
|
|
683
|
+
// searchMode on a graph-enabled KB, so it can be omitted. Passing it explicitly on a KB
|
|
684
|
+
// without GraphRAG throws GRAPH_SEARCH_NOT_ENABLED — no silent fallback to hybrid.
|
|
685
|
+
const graphChunks = await kb.search({
|
|
686
|
+
prompt: 'Which drugs does Aldous Corporation sell?',
|
|
687
|
+
searchMode: 'graph',
|
|
688
|
+
graphOptions: { seedLimit: 8, maxHops: 2 }, // Defaults shown; caps 25 and 3.
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
// Like search() but returns the full response, including the traversed subgraph
|
|
692
|
+
// (entities + relationships) — for debugging retrieval or explaining results.
|
|
693
|
+
const response = await kb.searchWithGraphContext({
|
|
694
|
+
prompt: 'Which drugs does Aldous Corporation sell?',
|
|
695
|
+
searchMode: 'graph',
|
|
696
|
+
graphOptions: { includeGraphContext: true },
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
// Scope ANY search mode to the documents under one theme or facet; composes with
|
|
700
|
+
// contextMetadataFilter. Value: a concept name (resolved server-side) or a facet nodeId.
|
|
701
|
+
// Unresolvable concepts fail with CONCEPT_NOT_FOUND plus the nearest matching names.
|
|
702
|
+
await kb.search({ prompt: 'What was the purchase price?', graphFilter: { underConcept: 'Acquisitions' } });
|
|
703
|
+
|
|
704
|
+
// Structure queries — one round trip each. Ops: 'overview' (themes/facets/counts map),
|
|
705
|
+
// 'resolve' | 'describe' | 'subtree' | 'docsUnder' | 'neighborhood' (take ref),
|
|
706
|
+
// 'conceptsOf' (takes contextId), 'pathBetween' (ref + refB -> relationship chain, or the
|
|
707
|
+
// concepts both sit under when no chain exists), 'globalSummary' (takes query).
|
|
708
|
+
const overview = await kb.queryGraph({ op: 'overview' });
|
|
709
|
+
const path = await kb.queryGraph({ op: 'pathBetween', ref: 'Aldous Corporation', refB: 'Zalofen' });
|
|
710
|
+
|
|
711
|
+
// Build state, coverage and cost: contextsIndexed/contextsTotal, entityCount,
|
|
712
|
+
// relationshipCount, topics/facets, structureStale/staleDocCount, buildJob,
|
|
713
|
+
// nextAutoBuildAt, ingestUsage/lastRebuildUsage (LLM tokens + estimated USD).
|
|
714
|
+
const status = await kb.getGraphStatus();
|
|
715
|
+
|
|
716
|
+
// Force a concept-layer build now instead of waiting for the debounced automatic build.
|
|
717
|
+
// Default mode 'structural' reuses extracted entities (cheap). mode: 'full' wipes the
|
|
718
|
+
// graph and re-extracts every chunk with the LLM — cost on the order of the initial
|
|
719
|
+
// ingest; only needed after changing entityTypes or extractionModel.
|
|
720
|
+
await kb.rebuildGraph();
|
|
721
|
+
|
|
722
|
+
// Bounded entity-graph slice for visualization (highest-degree entities and their
|
|
723
|
+
// relationships). nodeLimit default 200, max 1000; pass topicId to restrict to one theme.
|
|
724
|
+
const subgraph = await kb.exploreGraph({ nodeLimit: 100 });
|
|
452
725
|
```
|
|
453
726
|
|
|
727
|
+
Gotchas:
|
|
728
|
+
- Entity extraction is triggered by ingestion but runs asynchronously off the request path: `upsertContexts()` resolving does NOT mean the graph is ready — poll `getGraphStatus()` until `contextsIndexed === contextsTotal` before relying on graph retrieval for just-ingested documents. Poll with a timeout rather than an unbounded `while` loop: that equality can legitimately never hold, because contexts past the graph chunk cap (next bullet) are never stamped, and a pod restart mid-extraction leaves the remaining contexts unstamped until the debounced auto-build sweep re-extracts them. Treat a shortfall that stops moving as one of those cases, not as work still in flight — `graphIndexingSkippedContextIds` from the upsert identifies the cap case. No rebuild is needed for extraction; what lags further is the concept layer (themes/facets read by `queryGraph`), rebuilt after a quiet window (default 5 min, `autoBuildDebounceMs`) or a manual `rebuildGraph()`. Only one rebuild runs per KB at a time (`JOB_ALREADY_EXISTS`).
|
|
729
|
+
- Per-KB graph chunk cap (a deployment-level limit whose value can change — detect it via the signals, don't design around a fixed number): contexts beyond it stay fully vector/keyword-searchable but are not graph-indexed. Upsert results flag them — the batch `upsertContexts()` returns `graphIndexingSkippedContextIds`, only the singular `upsertContext()` sets the `graphIndexingSkipped` boolean — and `getGraphStatus()` shows `contextsIndexed < contextsTotal`.
|
|
730
|
+
- Only facet nodeIds and document contextIds are stable handles. Theme and entity ids churn on every rebuild — resolve names each time instead of persisting ids.
|
|
731
|
+
- Agents connected to a graph-enabled KB pick it up automatically: `'graph'` becomes the search tool's default mode, the tool description carries a compact graph overview, and the agent gets a `queryKnowledgeGraph` tool backed by the ops above (agent status broadcast: `'Querying Knowledge Base Graph'`). Exception: an active app/per-KB metadata visibility filter suppresses the graph navigation surface (overview block, `underConcept`, the `queryKnowledgeGraph` tool) so out-of-scope concepts/entities cannot leak; graph search mode stays available, filter-scoped.
|
|
732
|
+
|
|
733
|
+
### Bulk Ingestion
|
|
734
|
+
|
|
735
|
+
A durable, asynchronous lane for high-volume ingestion that runs contexts through provider batch APIs
|
|
736
|
+
instead of inline. Unlike `upsertContexts`, `bulkUpsertContexts` returns as soon as the request is
|
|
737
|
+
**staged** and never awaits completion.
|
|
738
|
+
|
|
739
|
+
```typescript
|
|
740
|
+
// 1. Stage contexts (files passed positionally for `type: 'file'` contexts)
|
|
741
|
+
const { jobId, contextIds, duplicates } = await kb.bulkUpsertContexts(contexts, files);
|
|
742
|
+
// contextIds is index-aligned with `contexts`. A context rejected as a content duplicate - the KB
|
|
743
|
+
// already holds identical content, or an earlier context in this same call did - still occupies its
|
|
744
|
+
// slot, so cross-reference `duplicates` by contextId to see what was actually staged.
|
|
745
|
+
|
|
746
|
+
// 2a. Watch it
|
|
747
|
+
kb.observeBulkIngestionJob(jobId).subscribe(s => console.log(s.state, s.counts, s.files));
|
|
748
|
+
|
|
749
|
+
// 2b. …or poll it
|
|
750
|
+
const status = await kb.getBulkIngestionJob(jobId);
|
|
751
|
+
// { state, counts, providerBatchIds, files }
|
|
752
|
+
|
|
753
|
+
// Cancel: already-finalized contexts are kept; the job goes to `cancelled` once in-flight work drains
|
|
754
|
+
await kb.cancelBulkIngestionJob(jobId);
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
**Large file sets — upload straight to storage.** The multipart path caps at **50 files, 100 MB per
|
|
758
|
+
file, and 256 MB of request body** (the body is buffered in memory server-side), so a single 150 MB
|
|
759
|
+
PDF is rejected there no matter how few files accompany it. Past any of the three, mint presigned
|
|
760
|
+
URLs and reference `stagedObjectKey` instead: core never holds the bytes and none of the multipart
|
|
761
|
+
caps apply. Two staging limits still bound every call whatever path the files arrived by: **10,000
|
|
762
|
+
contexts** and **256 MB of extracted content** — extracted, so base64 image payloads (~1.33x the raw
|
|
763
|
+
image bytes) count and a body that passed the multipart check can still be rejected during staging.
|
|
764
|
+
Split large corpora across jobs, which is what the CLI's `--batchSize` of 200 does:
|
|
765
|
+
|
|
766
|
+
```typescript
|
|
767
|
+
const { uploads } = await kb.createBulkUploadUrls(fileNames); // max 500 names per call
|
|
768
|
+
for (const u of uploads) {
|
|
769
|
+
// requiredHeaders is empty on S3, but Azure Blob rejects the PUT with 400 MissingRequiredHeader without it
|
|
770
|
+
await fetch(u.uploadUrl, { method: 'PUT', body: bytes, headers: u.requiredHeaders });
|
|
771
|
+
}
|
|
772
|
+
await kb.bulkUpsertContexts(uploads.map(u => ({ type: 'file', stagedObjectKey: u.stagedObjectKey, /* ... */ })));
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
**`observeBulkIngestionJob` semantics:**
|
|
776
|
+
- Completes on a terminal state (`completed`, `failed`, `cancelled`). A terminal `failed`/`cancelled`
|
|
777
|
+
arrives as a **normal emitted value** — the observable only errors on transport failure.
|
|
778
|
+
- Cold: each subscription registers its own server-side push. Share it (rxjs `share()`) for multiple
|
|
779
|
+
consumers.
|
|
780
|
+
- Re-subscribes automatically if the client connection id changes mid-subscription.
|
|
781
|
+
- If the app or KB is deleted mid-flight the job row is purged without a terminal push, so the
|
|
782
|
+
observable never completes — bound it (rxjs `timeout()`) when deletion is possible.
|
|
783
|
+
|
|
784
|
+
The CLI wraps this whole flow: `squid kb-upload --dir ./docs --knowledgeBase my-kb` (see
|
|
785
|
+
[backend.md](backend.md#cli-commands)).
|
|
786
|
+
|
|
787
|
+
### Spreadsheet Understanding
|
|
788
|
+
|
|
789
|
+
Spreadsheet files (`.csv`, `.tsv`, `.xlsx`, `.xlsm`, `.xls`, `.xlsb`) uploaded as file context take a dedicated ingestion lane: instead of chunking raw cell text, Squid extracts the workbook structure (sheets, headers, hidden sheets; charts and pivot tables where the format exposes them — absent for CSV/TSV and `.xls`, pivots also absent for `.xlsb`) and embeds a generated whole-workbook summary, so search results describe what a workbook contains.
|
|
790
|
+
|
|
791
|
+
When a connected knowledge base holds spreadsheet contexts, the agent automatically gets a `querySpreadsheetsWithAi` tool that runs Python in a sandbox against the actual uploaded files — for exact values (counts, sums, lookups, filtering, sorting), joins/comparisons across several workbooks in one call, and structure/provenance questions (which sheets feed live calculations, formulas vs hardcoded cells; formula/dependency inspection is fullest for `.xlsx`/`.xlsm`, partial for `.xls`, values-only for `.xlsb`, absent for CSV/TSV). No configuration needed, but the lane and the tool depend on the retained original file: with `discardOriginalFile: true` the spreadsheet ingests as plain extracted text and gets no query tool.
|
|
792
|
+
|
|
793
|
+
Spreadsheets attached to a chat via `fileUrls` similarly get a per-file Python query tool. Detection is by file extension from `fileName` (or the URL path when `fileName` is absent; query strings are stripped, so a signed URL ending in `.xlsx?sig=...` is detected) — set `fileName` only when the URL path itself lacks the extension, e.g. opaque download routes.
|
|
794
|
+
|
|
454
795
|
## AI Files
|
|
455
796
|
|
|
456
797
|
Manage files stored with AI providers (OpenAI, etc.) for use with agents.
|
|
@@ -553,7 +894,7 @@ const settings = await aiClient.getApplicationAiSettings();
|
|
|
553
894
|
|
|
554
895
|
// Set application AI settings
|
|
555
896
|
await aiClient.setApplicationAiSettings({
|
|
556
|
-
defaultModel: 'gpt-5-mini',
|
|
897
|
+
defaultModel: 'gpt-5.4-mini',
|
|
557
898
|
// ... other settings
|
|
558
899
|
});
|
|
559
900
|
|
|
@@ -562,8 +903,20 @@ await aiClient.setAiProviderApiKeySecret(
|
|
|
562
903
|
'openai', // providerType
|
|
563
904
|
'OPENAI_API_KEY' // secret key name
|
|
564
905
|
);
|
|
906
|
+
|
|
907
|
+
// List the chat models available to this application (Squid vendor models + custom integration models)
|
|
908
|
+
const models = await aiClient.listChatModels();
|
|
909
|
+
// Deprecated models (those carrying `replacedBy`) are excluded by default:
|
|
910
|
+
const withDeprecated = await aiClient.listChatModels({ includeDeprecated: true });
|
|
911
|
+
|
|
912
|
+
// List the AI functions the application exposes
|
|
913
|
+
const functions = await aiClient.listFunctions();
|
|
565
914
|
```
|
|
566
915
|
|
|
916
|
+
**Prefer `listChatModels()` over hardcoding a model name** — it reflects what the app can actually
|
|
917
|
+
use, including custom `openai_compatible`/Bedrock/Vertex integration models, and marks deprecated
|
|
918
|
+
models via `replacedBy`.
|
|
919
|
+
|
|
567
920
|
## Backend Decorators
|
|
568
921
|
|
|
569
922
|
### @aiFunction
|
|
@@ -619,6 +972,36 @@ async bookHotel(args: BookingArgs): Promise<string> {
|
|
|
619
972
|
|
|
620
973
|
For `@secureAiAgent` and `@secureAiQuery`, see [security.md](security.md#ai-security).
|
|
621
974
|
|
|
975
|
+
## Source-Code Analysis
|
|
976
|
+
|
|
977
|
+
An agent can answer questions about real repositories. Enable code analysis on a `github` or
|
|
978
|
+
`bitbucket` integration; the agent then gets an `analyzeCode` tool that clones the selected
|
|
979
|
+
repositories into an isolated workspace and runs a CLI coding agent over them.
|
|
980
|
+
|
|
981
|
+
```typescript
|
|
982
|
+
// 1. Discover the repositories an integration can reach (admin client - requires an API key)
|
|
983
|
+
const integrations = squid.admin().integrations();
|
|
984
|
+
const { repositories } = await integrations.discoverSourceRepositories('my-github');
|
|
985
|
+
// repositories: { provider, providerRepositoryId, fullName, cloneUrl, webUrl, defaultBranch }[]
|
|
986
|
+
|
|
987
|
+
// Or, before the integration is saved:
|
|
988
|
+
const draft = await integrations.discoverSourceRepositoriesFromDraft(integrationInfo);
|
|
989
|
+
|
|
990
|
+
// 2. Select repositories on the integration and configure the agent's analysis options
|
|
991
|
+
// (AiAgentSourceCodeIntegrationOptions):
|
|
992
|
+
// codeAnalysisEnabled, analyzerModel, analyzerModelId, analyzerReasoningEffort, analyzerInstructions
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
**Constants** (exported from both `@squidcloud/client` and `@squidcloud/backend`):
|
|
996
|
+
- `SOURCE_CODE_ANALYZE_FUNCTION_ID` = `'analyzeCode'` — the AI function id
|
|
997
|
+
- `SOURCE_CODE_ANALYZER_MODELS` = `['claude-code', 'codex']` — the CLI providers
|
|
998
|
+
- `SOURCE_CONTROL_PROVIDERS` / `SOURCE_CODE_INTEGRATION_TYPES` = `['github', 'bitbucket']`
|
|
999
|
+
- `SOURCE_CODE_MAX_REPOSITORIES` = 20
|
|
1000
|
+
- `SOURCE_CODE_MAX_SUPPLEMENTAL_INSTRUCTIONS_LENGTH` = 20,000 characters per instruction field
|
|
1001
|
+
|
|
1002
|
+
Selecting repositories and enabling analysis is usually done in the Console — the SDK path exists for
|
|
1003
|
+
automation.
|
|
1004
|
+
|
|
622
1005
|
## MCP (Model Context Protocol)
|
|
623
1006
|
|
|
624
1007
|
Squid supports MCP for extending agent capabilities.
|
|
@@ -766,7 +1149,7 @@ configureAiFunctions(request: AiFunctionsConfiguratorRequest): AiFunctionsConfig
|
|
|
766
1149
|
## Best Practices
|
|
767
1150
|
|
|
768
1151
|
1. **NEVER invoke LLMs directly** - Do NOT import `openai`, `@anthropic-ai/sdk`, or call LLM APIs directly. ALWAYS use Squid AI Agents via `squid.ai().agent()`. Squid agents provide built-in security, memory, streaming, function calling, guardrails, and model management. If you need an LLM call, create/use an AI agent.
|
|
769
|
-
2. **Use memoryOptions for AI conversations** -
|
|
1152
|
+
2. **Use memoryOptions for AI conversations** - `memoryOptions.memoryId` names the conversation, `memoryOptions.memoryMode` controls history
|
|
770
1153
|
3. **Agent IDs are permanent** - Plan naming carefully, cannot be changed after creation
|
|
771
1154
|
4. **Memory is enabled by default** - Set `memoryMode: 'none'` to disable conversation history
|
|
772
1155
|
5. **Streaming behavior differs with connected resources** - No resources: streams token-by-token; Has resources: emits once with complete response
|
|
@@ -13,6 +13,15 @@ All API endpoints require an API key. Include it in the request headers:
|
|
|
13
13
|
Authorization: Bearer YOUR_API_KEY
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
Two security schemes exist:
|
|
17
|
+
|
|
18
|
+
| Scheme | Header | Scope |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| `apiKeyAuth` | `x-app-api-key` | The application API key. Accepted everywhere. |
|
|
21
|
+
| `agentApiKeyAuth` | `x-squid-agent-api-key` | An **agent-scoped** key, accepted by the agent `ask` endpoints and by an agent's MCP endpoint when `requireApiKey` is set. |
|
|
22
|
+
|
|
23
|
+
Get an agent key with `squid.ai().agent(id).getApiKey()` / `regenerateApiKey()`.
|
|
24
|
+
|
|
16
25
|
## API Categories
|
|
17
26
|
|
|
18
27
|
### [Agent API](https://docs.getsquid.ai/reference-docs/api/#tag/Agent)
|
|
@@ -26,12 +35,27 @@ Manage AI agents. SDK: [ai.md](ai.md)
|
|
|
26
35
|
- `POST /agent/updateGuardrails` - Configure guardrails
|
|
27
36
|
- `POST /agent/updateCustomGuardrails` - Set custom guardrails
|
|
28
37
|
- `POST /agent/deleteCustomGuardrails` - Remove custom guardrails
|
|
38
|
+
- `POST /agent/setAgentOptionInPath` - Set any agent option by path; how the SDK's `updatePii()` writes the `pii` policy (no dedicated PII endpoint)
|
|
29
39
|
- `POST /agent/ask` - Send prompt, get response
|
|
30
40
|
- `POST /agent/askWithAnnotations` - Get response with annotations
|
|
41
|
+
- `GET /agent/listAgents` - List the application's agents
|
|
31
42
|
- `GET /agent/revisions/{agentId}` - List revisions
|
|
32
43
|
- `POST /agent/restoreRevision` - Restore revision
|
|
33
44
|
- `POST /agent/deleteRevision` - Delete revision
|
|
34
45
|
|
|
46
|
+
The `ask` / `askWithAnnotations` endpoints accept `agentApiKeyAuth` in addition to `apiKeyAuth`.
|
|
47
|
+
|
|
48
|
+
Exposing an agent as an MCP server (`updateMcpServer()`) and generating its MCP descriptions
|
|
49
|
+
(`generateMcpDescriptions()`) are SDK-only — they are not part of the published REST surface.
|
|
50
|
+
|
|
51
|
+
### AiFunction API
|
|
52
|
+
List the AI functions the application exposes. SDK: [ai.md](ai.md)
|
|
53
|
+
- `GET /ai/function/listFunctions` - List AI functions
|
|
54
|
+
|
|
55
|
+
### AiSettings API
|
|
56
|
+
Application-level AI settings. SDK: [ai.md](ai.md)
|
|
57
|
+
- `GET /ai/settings/listChatModels?includeDeprecated=false` - Chat models available to the app (Squid vendor models + custom integration models)
|
|
58
|
+
|
|
35
59
|
### [AI Audio API](https://docs.getsquid.ai/reference-docs/api/#tag/AI-Audio)
|
|
36
60
|
Transcribe audio, text-to-speech. SDK: [ai.md](ai.md)
|
|
37
61
|
- `POST /audio/transcribe` - Transcribe audio to text
|
|
@@ -51,7 +75,17 @@ Manage knowledge bases for RAG. SDK: [ai.md](ai.md)
|
|
|
51
75
|
- `POST /knowledge-base/deleteContexts` - Delete contexts
|
|
52
76
|
- `GET /knowledge-base/getContext/{knowledgeBaseId}/{contextId}` - Get context
|
|
53
77
|
- `GET /knowledge-base/listContexts/{knowledgeBaseId}` - List contexts
|
|
54
|
-
- `
|
|
78
|
+
- `GET /knowledge-base/listContextsPage/{knowledgeBaseId}` - List a page of contexts (offset/limit/search)
|
|
79
|
+
- `GET /knowledge-base/listKnowledgeBases` - List the application's knowledge bases
|
|
80
|
+
- `POST /knowledge-base/search` - Search (vector/hybrid/keyword/graph via `searchMode`; graph fields in the response when requested)
|
|
81
|
+
- `POST /knowledge-base/queryGraph` - Query graph structure (overview, resolve, describe, subtree, docsUnder, conceptsOf, neighborhood, pathBetween, globalSummary)
|
|
82
|
+
- `GET /knowledge-base/getGraphStatus/{knowledgeBaseId}` - Graph build status, coverage, and LLM cost
|
|
83
|
+
- `POST /knowledge-base/rebuildGraph` - Enqueue a graph rebuild (`structural` or `full`)
|
|
84
|
+
- `POST /knowledge-base/exploreGraph` - Bounded entity-graph slice for visualization
|
|
85
|
+
|
|
86
|
+
Literal scan (`kb.grep()`) and bulk ingestion (`kb.bulkUpsertContexts()` and friends) are SDK-only —
|
|
87
|
+
they are not part of the published REST surface. For bulk ingestion from a machine, use the
|
|
88
|
+
`squid kb-upload` CLI command.
|
|
55
89
|
|
|
56
90
|
### [Matchmaking API](https://docs.getsquid.ai/reference-docs/api/#tag/Matchmaking) *(deprecated)*
|
|
57
91
|
Use `knowledgeBase().searchContextsWith*()` instead.
|
|
@@ -85,3 +119,14 @@ Create PDFs, extract data from documents. SDK: [client.md](client.md)
|
|
|
85
119
|
| Knowledge Bases | `squid.ai().knowledgeBase()` | KnowledgeBase API |
|
|
86
120
|
| Web Utilities | `squid.web()` | Web Utilities API |
|
|
87
121
|
| Extraction | `squid.extraction()` | Extraction Utilities API |
|
|
122
|
+
| Chat models / AI functions | `squid.ai().listChatModels()` / `listFunctions()` | AiSettings API / AiFunction API |
|
|
123
|
+
| Events | `squid.events().emit()` | SDK only |
|
|
124
|
+
| LangGraph | `squid.langGraph(id)` | SDK only |
|
|
125
|
+
| KB literal scan, bulk ingestion | `kb.grep()`, `kb.bulkUpsertContexts()` | SDK only (bulk also via `squid kb-upload`) |
|
|
126
|
+
|
|
127
|
+
## Chat Options: Naming a Conversation
|
|
128
|
+
|
|
129
|
+
The agent endpoints take conversation identity and history control under `memoryOptions`:
|
|
130
|
+
`memoryOptions.memoryId` names the conversation, and `memoryOptions.memoryMode`
|
|
131
|
+
(`'none' | 'read-only' | 'read-write'`) controls whether history is read and written. An unrecognized
|
|
132
|
+
option key is rejected with a **400** naming the option that carries it.
|
|
@@ -18,6 +18,7 @@ Docs: https://docs.getsquid.ai/reference-docs/backend/
|
|
|
18
18
|
- Schedulers (@scheduler)
|
|
19
19
|
- Rate Limiting (@limits)
|
|
20
20
|
- Queue Message Handlers (@onQueueMessage)
|
|
21
|
+
- Events (@eventHandler)
|
|
21
22
|
- Client Connection State (@clientConnectionStateHandler)
|
|
22
23
|
- Cross-Service Communication (squidInject)
|
|
23
24
|
- Using Squid Client in Backend
|
|
@@ -37,8 +38,10 @@ squid init backend --appId YOUR_APP_ID --apiKey YOUR_API_KEY --environmentId dev
|
|
|
37
38
|
|
|
38
39
|
**`squid start`** - Runs backend locally with hot-reload. Connects to Squid Cloud via reverse proxy.
|
|
39
40
|
```bash
|
|
40
|
-
cd backend && squid start
|
|
41
|
+
cd backend && squid start [--printBundle]
|
|
41
42
|
```
|
|
43
|
+
`--printBundle` makes the local dev server log the full application bundle data on startup instead of
|
|
44
|
+
just the webhooks — useful when checking what the build actually registered.
|
|
42
45
|
|
|
43
46
|
**`squid deploy`** - Builds and deploys to Squid Cloud.
|
|
44
47
|
```bash
|
|
@@ -50,6 +53,20 @@ squid deploy [--apiKey KEY] [--environmentId prod] [--skipBuild]
|
|
|
50
53
|
squid build [--dev] [--skip-version-check]
|
|
51
54
|
```
|
|
52
55
|
|
|
56
|
+
**`squid kb-upload`** - Bulk-ingests a local directory into a knowledge base using the durable bulk
|
|
57
|
+
ingestion pipeline (presigned uploads + provider batch APIs). Walks `--dir` recursively, stages files
|
|
58
|
+
in batches, and polls each job to completion.
|
|
59
|
+
```bash
|
|
60
|
+
squid kb-upload --dir ./docs --knowledgeBase my-kb
|
|
61
|
+
```
|
|
62
|
+
Options: `--dir` (required), `--knowledgeBase` (required), `--appId`/`--apiKey`/`--region`/`--environmentId`
|
|
63
|
+
(fall back to `SQUID_APP_ID`, `SQUID_API_KEY`, `SQUID_REGION`, `SQUID_ENVIRONMENT_ID`),
|
|
64
|
+
`--batchSize` (files per job, default 200, max 1000), `--extensions` (comma-separated allow-list;
|
|
65
|
+
defaults to pdf, docx, txt, md, html, csv, xlsx, xls, xlsm, xlsb, pptx), `--dryRun` (list files and
|
|
66
|
+
exit without contacting the server), `--timeoutMinutes` (per-job wait budget, default 120 — on
|
|
67
|
+
timeout the CLI reports the job as still running server-side and moves on).
|
|
68
|
+
Ctrl-C cancels the in-flight job. See [ai.md](ai.md#bulk-ingestion) for the SDK equivalent.
|
|
69
|
+
|
|
53
70
|
**Extended logging** - Add to `.env`:
|
|
54
71
|
```env
|
|
55
72
|
SQUID_LOG_TYPES=QUERY,MUTATION,AI,API,ERROR
|
|
@@ -500,6 +517,33 @@ export class MyService extends SquidService {
|
|
|
500
517
|
- To secure the topic, use `@secureTopic` — see [security.md](security.md)
|
|
501
518
|
- The handler receives each message individually (not batched)
|
|
502
519
|
|
|
520
|
+
## Events (@eventHandler)
|
|
521
|
+
|
|
522
|
+
`@eventHandler(type)` subscribes a backend function to events emitted with
|
|
523
|
+
`squid.events().emit(...)` (see [client.md](client.md#events)). Unlike `@onQueueMessage`, **every**
|
|
524
|
+
handler registered for the type receives the event, so one event can fan out to several services.
|
|
525
|
+
|
|
526
|
+
```typescript
|
|
527
|
+
import { SquidService, eventHandler } from '@squidcloud/backend';
|
|
528
|
+
import { TriggerEvent } from '@squidcloud/client';
|
|
529
|
+
|
|
530
|
+
export class OrderEventsService extends SquidService {
|
|
531
|
+
@eventHandler<{ orderId: string; total: number }>('order.created')
|
|
532
|
+
async onOrderCreated(event: TriggerEvent<{ orderId: string; total: number }>): Promise<void> {
|
|
533
|
+
console.log(event.id); // unique event id
|
|
534
|
+
console.log(event.type); // 'order.created'
|
|
535
|
+
console.log(event.payload.orderId);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
**Notes:**
|
|
541
|
+
- Delivery is at-least-once with **no ordering guarantee** — handlers must be idempotent and
|
|
542
|
+
order-independent.
|
|
543
|
+
- Emitting requires API key authentication; a user token is rejected.
|
|
544
|
+
- Multiple subscribers per type are supported (that is the point — use `@onQueueMessage` when you
|
|
545
|
+
want queue semantics instead).
|
|
546
|
+
|
|
503
547
|
## Client Connection State (@clientConnectionStateHandler)
|
|
504
548
|
|
|
505
549
|
Handle client connection and disconnection events.
|
|
@@ -620,6 +664,18 @@ export class MyService extends SquidService {
|
|
|
620
664
|
}
|
|
621
665
|
```
|
|
622
666
|
|
|
667
|
+
**`getSquid()` / `getPassiveSquid()`** — `getSquid()` returns the same instance backing `this.squid`.
|
|
668
|
+
`getPassiveSquid()` returns a dedicated **passive-mode** instance (HTTP-only, no WebSocket), kept
|
|
669
|
+
separate from the main one. Use it for one-off HTTP calls that should not open or keep a socket.
|
|
670
|
+
|
|
671
|
+
```typescript
|
|
672
|
+
import { getSquid, getPassiveSquid } from '@squidcloud/backend';
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
**Experimental:** `this.workspace` (a `WorkspaceClient`) gives a backend-only scratch directory
|
|
676
|
+
synced across pod replicas via the shared workspace server — distinct from the tenant's shared
|
|
677
|
+
storage.
|
|
678
|
+
|
|
623
679
|
## File Handling
|
|
624
680
|
|
|
625
681
|
```typescript
|
|
@@ -763,7 +763,7 @@ declare namespace JSX {
|
|
|
763
763
|
squid-ai-instructions="Be concise and professional."
|
|
764
764
|
squid-ai-functions="getOrderStatus,lookupAccount"
|
|
765
765
|
squid-ai-connected-agents='[{"agentId":"billing-agent","description":"Handles billing questions"}]'
|
|
766
|
-
squid-ai-override-model="gpt-5-mini"
|
|
766
|
+
squid-ai-override-model="gpt-5.4-mini"
|
|
767
767
|
squid-ai-temperature="0.7"
|
|
768
768
|
squid-ai-max-tokens="2000"
|
|
769
769
|
squid-ai-agent-chat-options='{
|
|
@@ -18,6 +18,8 @@ Docs: https://docs.getsquid.ai/reference-docs/typescript-client/
|
|
|
18
18
|
- Web
|
|
19
19
|
- Extraction
|
|
20
20
|
- Jobs
|
|
21
|
+
- Events
|
|
22
|
+
- LangGraph
|
|
21
23
|
- Observability & Metrics
|
|
22
24
|
- Custom Notifications
|
|
23
25
|
|
|
@@ -74,7 +76,7 @@ Every Squid application includes the **Essentials Connector** - a built-in integ
|
|
|
74
76
|
|---------|--------|-------------|------|
|
|
75
77
|
| **Web Utilities** | `squid.web()` | AI-powered web search, URL content extraction, short URLs | [Web section](#web) |
|
|
76
78
|
| **AI Agents** | `squid.ai().agent()` | Chat with built-in or custom AI agents | [ai.md](ai.md) |
|
|
77
|
-
| **Knowledge Bases** | `squid.ai().knowledgeBase()` | RAG with semantic
|
|
79
|
+
| **Knowledge Bases** | `squid.ai().knowledgeBase()` | RAG with semantic, keyword, and knowledge-graph search plus reranking | [ai.md](ai.md) |
|
|
78
80
|
| **Image Generation** | `squid.ai().image()` | Generate images with DALL-E | [ai.md](ai.md) |
|
|
79
81
|
| **Audio** | `squid.ai().audio()` | Transcription and text-to-speech | [ai.md](ai.md) |
|
|
80
82
|
| **PDF/Extraction** | `squid.extraction()` | Create PDFs, extract data from documents | [Extraction section](#extraction) |
|
|
@@ -324,9 +326,9 @@ const result = await squid.withLock('payment-processing', async (lock) => {
|
|
|
324
326
|
- `maxHoldTimeMillis` (number, default: no limit) - Max time in ms the lock can be held before automatic release. If not set, the lock is held until explicitly released or the connection is lost.
|
|
325
327
|
|
|
326
328
|
**DistributedLock methods:**
|
|
327
|
-
- `release()` - Release the lock
|
|
329
|
+
- `release()` - Release the lock. The lock is considered released immediately; the message to the server is sent asynchronously.
|
|
328
330
|
- `isReleased()` - Check if already released
|
|
329
|
-
- `observeRelease()` - Observable that emits when lock is released (including unexpected release due to connection loss)
|
|
331
|
+
- `observeRelease()` - Observable that emits when lock is released (including unexpected release due to connection loss). It emits as soon as the lock is considered released **locally**, without waiting for the server to confirm, and it replays — a subscriber that attaches after the release still learns about it.
|
|
330
332
|
- `resourceId` - The mutex name
|
|
331
333
|
- `lockId` - Unique lock instance ID
|
|
332
334
|
|
|
@@ -344,6 +346,12 @@ const web = squid.web();
|
|
|
344
346
|
// AI-powered web search
|
|
345
347
|
const results = await web.aiSearch('latest AI developments');
|
|
346
348
|
|
|
349
|
+
// aiSearch(query, abortSignal?, allowedDomains?)
|
|
350
|
+
// - abortSignal cancels the in-flight HTTP request, bounding a single call.
|
|
351
|
+
// - allowedDomains restricts the search (subdomains included); every cited URL comes from that list.
|
|
352
|
+
const controller = new AbortController();
|
|
353
|
+
const scoped = await web.aiSearch('pricing changes', controller.signal, ['getsquid.ai', 'docs.getsquid.ai']);
|
|
354
|
+
|
|
347
355
|
// Get URL content (as markdown)
|
|
348
356
|
const content = await web.getUrlContent('https://example.com/article');
|
|
349
357
|
|
|
@@ -416,6 +424,69 @@ if (job?.status === 'completed') {
|
|
|
416
424
|
const result = await jobClient.awaitJob<Result>('job-123');
|
|
417
425
|
```
|
|
418
426
|
|
|
427
|
+
### Driving a job from your own code
|
|
428
|
+
|
|
429
|
+
A job can also be created and resolved by the caller. Use this when a long operation runs outside
|
|
430
|
+
Squid and clients should still `awaitJob` on it. These three methods require an API key.
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
const jobId = crypto.randomUUID();
|
|
434
|
+
|
|
435
|
+
await jobClient.startJob(jobId); // mark the job as running
|
|
436
|
+
await jobClient.completeJob(jobId, { rows: 42 }); // resolve waiters with a result
|
|
437
|
+
await jobClient.failJob(jobId, 'Upstream timed out'); // reject waiters with an error
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
## Events
|
|
441
|
+
|
|
442
|
+
Emit a generic event that every backend function declared with `@eventHandler` for the same type
|
|
443
|
+
receives. Delivery is durable and at-least-once with **no ordering guarantee**, so handlers must be
|
|
444
|
+
idempotent and order-independent. `emit()` resolves once the event is enqueued, not once handlers ran.
|
|
445
|
+
|
|
446
|
+
Requires API key authentication — calls authenticated with a user token are rejected.
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
import { generateUUID } from '@squidcloud/client';
|
|
450
|
+
|
|
451
|
+
await squid.events().emit({
|
|
452
|
+
id: generateUUID(), // unique event id
|
|
453
|
+
type: 'order.created', // selects the @eventHandler subscribers
|
|
454
|
+
payload: { orderId: 'o-1', total: 99.5 },
|
|
455
|
+
});
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
Handle it in the backend — see [backend.md](backend.md#events-eventhandler).
|
|
459
|
+
|
|
460
|
+
## LangGraph
|
|
461
|
+
|
|
462
|
+
Invoke Python LangGraph graphs defined in a Python backend. Get a reference with
|
|
463
|
+
`squid.langGraph(graphId)`.
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
const graph = squid.langGraph('support-graph');
|
|
467
|
+
|
|
468
|
+
// Run and wait for the result (new thread when threadId is omitted)
|
|
469
|
+
const result = await graph.invoke({ input: { question: 'Where is my order?' } });
|
|
470
|
+
// result: { ok, threadId, status: 'completed' | 'interrupted', state, next }
|
|
471
|
+
|
|
472
|
+
// Run without holding a promise — returns a jobId to await later
|
|
473
|
+
const jobId = await graph.invokeAsync({ threadId: 'thread-1', input: { question: '...' } });
|
|
474
|
+
const later = await squid.job().awaitJob(jobId);
|
|
475
|
+
|
|
476
|
+
// Resume a graph paused on interrupt()
|
|
477
|
+
const resumed = await graph.resume('thread-1', { approved: true });
|
|
478
|
+
const resumeJobId = await graph.resumeAsync('thread-1', { approved: true });
|
|
479
|
+
|
|
480
|
+
// Inspect and clean up thread state
|
|
481
|
+
const state = await graph.getState('thread-1'); // { ok, threadId, state, next }
|
|
482
|
+
await graph.deleteThread('thread-1'); // idempotent; next run starts fresh
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Graph execution caps at the tenant IPC timeout (currently 4 minutes) for every variant — the `Async`
|
|
486
|
+
methods only free the caller from holding the promise.
|
|
487
|
+
|
|
488
|
+
Secure invocations with the backend `@secureLangGraph` decorator — see [security.md](security.md#securelanggraph).
|
|
489
|
+
|
|
419
490
|
## Observability & Metrics
|
|
420
491
|
|
|
421
492
|
Report and query custom metrics for monitoring application performance.
|
|
@@ -48,6 +48,7 @@ The AI Studio provides visual tools for building and testing AI agents:
|
|
|
48
48
|
### Agent Configuration
|
|
49
49
|
- Create and configure AI agents visually
|
|
50
50
|
- Set agent instructions, models, and guardrails
|
|
51
|
+
- Refuse prompts containing PII under Guardrails → Prompt Privacy (toggle, a checkbox per kind, and custom rules)
|
|
51
52
|
- Configure agent memory and conversation settings
|
|
52
53
|
- Connect agents to functions, knowledge bases, and integrations
|
|
53
54
|
|
|
@@ -76,6 +77,10 @@ The AI Studio provides visual tools for building and testing AI agents:
|
|
|
76
77
|
- Search and preview indexed content
|
|
77
78
|
- Monitor indexing status
|
|
78
79
|
|
|
80
|
+
### Knowledge Graph
|
|
81
|
+
- Enable via the **Knowledge Graph** toggle when creating a knowledge base with **Vector Store** set to `mongoAtlas` (disabled for other stores — the store is immutable after creation), or later by editing an Atlas knowledge base
|
|
82
|
+
- Knowledge base page shows a **Knowledge Graph** card: last build time, documents changed since it, next automatic build ETA, and a **Build now** button (runs a structural build)
|
|
83
|
+
|
|
79
84
|
## Integrations Setup
|
|
80
85
|
|
|
81
86
|
### Database Connections
|
|
@@ -8,6 +8,8 @@ Squid provides database functionality similar to Firestore but more powerful, wi
|
|
|
8
8
|
- CRUD Operations
|
|
9
9
|
- Real-time Subscriptions
|
|
10
10
|
- Query Operators
|
|
11
|
+
- Querying by Document ID
|
|
12
|
+
- Deleting by Query
|
|
11
13
|
- OR Queries
|
|
12
14
|
- Join Queries
|
|
13
15
|
- Dereference
|
|
@@ -231,6 +233,52 @@ const sorted = await users.query()
|
|
|
231
233
|
|
|
232
234
|
**Note:** `offset()` does NOT exist - use `paginate()` for pagination.
|
|
233
235
|
|
|
236
|
+
## Querying by Document ID
|
|
237
|
+
|
|
238
|
+
Filter on the document id with the `docId()` / `docIds()` shortcuts, or with `__docId__` (the
|
|
239
|
+
`DOC_ID_FIELD` constant) in `eq`/`neq`/`in`/`nin`/`where`. This works for both single- and
|
|
240
|
+
composite-key collections: pass the primitive for a single-field key, or the key object for a
|
|
241
|
+
composite one.
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
import { DOC_ID_FIELD } from '@squidcloud/client';
|
|
245
|
+
|
|
246
|
+
// Single-key collection
|
|
247
|
+
const one = await users.query().docId('user-1').snapshot();
|
|
248
|
+
const some = await users.query().docIds(['user-1', 'user-2']).snapshot();
|
|
249
|
+
|
|
250
|
+
// Composite-key collection - pass the key object
|
|
251
|
+
const line = await orderLines.query().docId({ orderId: 'o-1', lineNo: 3 }).snapshot();
|
|
252
|
+
|
|
253
|
+
// Equivalent explicit forms
|
|
254
|
+
await users.query().eq(DOC_ID_FIELD, 'user-1').snapshot();
|
|
255
|
+
await users.query().in(DOC_ID_FIELD, ['user-1', 'user-2']).snapshot();
|
|
256
|
+
await users.query().where(DOC_ID_FIELD, '==', 'user-1').snapshot();
|
|
257
|
+
await users.query().where(DOC_ID_FIELD, 'in', ['user-1', 'user-2']).snapshot();
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
`docId()` is `eq(DOC_ID_FIELD, ...)` and `docIds()` is `in(DOC_ID_FIELD, ...)` — use them instead of
|
|
261
|
+
fetching a page and filtering client-side.
|
|
262
|
+
|
|
263
|
+
## Deleting by Query
|
|
264
|
+
|
|
265
|
+
`delete()` on a query deletes every document the query selects and returns how many were selected.
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
const { deletedCount } = await users.query()
|
|
269
|
+
.eq('status', 'inactive')
|
|
270
|
+
.lt('lastLoginAt', cutoff)
|
|
271
|
+
.delete();
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
`sortBy`, `limit`, and `limitBy` are rejected — `delete()` throws if the query carries any of them.
|
|
275
|
+
A query whose `in` condition is an empty array selects nothing, so it deletes nothing and returns
|
|
276
|
+
`{ deletedCount: 0 }`. An empty `nin` behaves the opposite way: it contributes no condition at all,
|
|
277
|
+
so it does not restrict the delete — `users.query().nin('id', []).delete()` deletes everything the
|
|
278
|
+
remaining conditions select, the whole collection when there are none. Narrow an empty exclusion list
|
|
279
|
+
in your own code before calling `delete()`. Deletes go through the collection's `delete` security
|
|
280
|
+
rules, same as `doc.delete()`.
|
|
281
|
+
|
|
234
282
|
## OR Queries
|
|
235
283
|
|
|
236
284
|
Combine multiple queries with OR logic:
|
|
@@ -10,7 +10,7 @@ Squid provides a comprehensive security model using backend decorators. Every op
|
|
|
10
10
|
- Storage Security
|
|
11
11
|
- API Security
|
|
12
12
|
- Native Query Security
|
|
13
|
-
- AI Security
|
|
13
|
+
- AI Security (including @secureLangGraph)
|
|
14
14
|
- Distributed Lock Security
|
|
15
15
|
- GraphQL Security
|
|
16
16
|
- Authentication Patterns
|
|
@@ -132,6 +132,34 @@ context.affectsPath(path) // Check if specific field path was modified
|
|
|
132
132
|
|
|
133
133
|
Both types are exported from `@squidcloud/backend`.
|
|
134
134
|
|
|
135
|
+
### @publicCollection
|
|
136
|
+
|
|
137
|
+
A **class** decorator that declares reads of a collection as intentionally public. Public reads
|
|
138
|
+
bypass security-rule evaluation entirely: Core serves them without calling backend code, so any
|
|
139
|
+
client can read the collection, **including unauthenticated ones**. Use it only for data that is
|
|
140
|
+
safe to expose to everyone; prefer a `@secureCollection` read rule when access depends on the caller.
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { SquidService, publicCollection, secureCollection, MutationContext } from '@squidcloud/backend';
|
|
144
|
+
|
|
145
|
+
// Anyone can read `articles`, but only authenticated users can write to it.
|
|
146
|
+
@publicCollection('articles', 'read')
|
|
147
|
+
export class ArticleService extends SquidService {
|
|
148
|
+
@secureCollection('articles', 'write')
|
|
149
|
+
async secureArticlesWrite(context: MutationContext): Promise<boolean> {
|
|
150
|
+
return this.isAuthenticated();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- Signature: `publicCollection(collectionName, 'read', integrationId?)`. `integrationId` defaults to
|
|
156
|
+
the built-in database.
|
|
157
|
+
- Only `read` is supported. Writes always go through their security rules, so a public-read
|
|
158
|
+
collection can still restrict `insert`/`update`/`delete`/`write`.
|
|
159
|
+
- **Secure wins on conflict:** if a `@secureCollection` or `@secureDatabase` `read` (or `all`) rule
|
|
160
|
+
also applies to the same collection, that rule takes precedence at runtime and the public
|
|
161
|
+
declaration is ignored.
|
|
162
|
+
|
|
135
163
|
## Queue Security
|
|
136
164
|
|
|
137
165
|
### @secureTopic
|
|
@@ -270,6 +298,51 @@ allowAllAgents(): boolean {
|
|
|
270
298
|
}
|
|
271
299
|
```
|
|
272
300
|
|
|
301
|
+
### @secureLangGraph
|
|
302
|
+
|
|
303
|
+
Secures LangGraph invocations made with `squid.langGraph(graphId)` (see
|
|
304
|
+
[client.md](client.md#langgraph)).
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
import { SquidService, secureLangGraph, SecureLangGraphContext } from '@squidcloud/backend';
|
|
308
|
+
|
|
309
|
+
// Secure a specific graph
|
|
310
|
+
@secureLangGraph('support-graph')
|
|
311
|
+
allowSupportGraph(context: SecureLangGraphContext): boolean {
|
|
312
|
+
// context.operation: 'invoke' | 'resume' | 'getState' | 'deleteThread'
|
|
313
|
+
const userId = this.getUserAuth()?.userId;
|
|
314
|
+
if (!userId) return false;
|
|
315
|
+
// This app mints thread ids as `${userId}:${conversation}` and passes one on every call, so
|
|
316
|
+
// ownership is derived from threadId. Check it on every operation, not just deleteThread:
|
|
317
|
+
// threadId is a client-supplied field everywhere and threads carry no owner of their own, so
|
|
318
|
+
// this rule is all that stops one authenticated user from reading, extending, or deleting
|
|
319
|
+
// another's thread. An invoke that omits threadId is refused too: the runtime would mint a bare
|
|
320
|
+
// UUID, and no later call on that thread could satisfy this rule.
|
|
321
|
+
// Never derive ownership from context.input — it is populated on invoke only, so the comparison
|
|
322
|
+
// would be undefined === undefined, which passes for exactly the callers it should refuse.
|
|
323
|
+
return !!context.threadId && context.threadId.startsWith(`${userId}:`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Secure all graphs
|
|
327
|
+
@secureLangGraph()
|
|
328
|
+
allowAllGraphs(context: SecureLangGraphContext): boolean {
|
|
329
|
+
return this.isAuthenticated();
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
`SecureLangGraphContext` carries `operation`, `graphId`, `threadId`, `input`, and `resumePayload`.
|
|
334
|
+
Only the fields the triggering request itself carries are populated: `input` on `invoke` only,
|
|
335
|
+
`resumePayload` on `resume` only, and `threadId` on everything except an `invoke` that starts a new
|
|
336
|
+
thread. A rule that reads a field its operation does not carry sees `undefined`, so write ownership
|
|
337
|
+
checks that cannot pass on `undefined` — otherwise an unauthenticated caller, whose `getUserAuth()`
|
|
338
|
+
is also `undefined`, is the one the rule lets through.
|
|
339
|
+
|
|
340
|
+
A thread has no owner at the storage layer and `threadId` reaches the rule straight from the client
|
|
341
|
+
body on every operation, so the `@secureLangGraph` rule is the only per-thread access control there
|
|
342
|
+
is. The `allowAllGraphs` shape above authenticates the caller but does not scope them to their own
|
|
343
|
+
threads — enough for a single-tenant graph, not for one where users must not see each other's
|
|
344
|
+
conversations.
|
|
345
|
+
|
|
273
346
|
## Distributed Lock Security
|
|
274
347
|
|
|
275
348
|
### @secureDistributedLock
|
|
@@ -12,7 +12,7 @@ Each Squid SaaS connector provides built-in AI functions that AI agents can call
|
|
|
12
12
|
await squid.ai().agent('my-agent').upsert({
|
|
13
13
|
description: 'Support agent',
|
|
14
14
|
options: {
|
|
15
|
-
model: 'gpt-5-mini',
|
|
15
|
+
model: 'gpt-5.4-mini',
|
|
16
16
|
instructions: 'You are a support agent that can search tickets and send messages.'
|
|
17
17
|
}
|
|
18
18
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@squidcloud/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.488",
|
|
4
4
|
"description": "The Squid CLI",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"node": ">=18.0.0"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@squidcloud/local-backend": "^1.0.
|
|
31
|
+
"@squidcloud/local-backend": "^1.0.488",
|
|
32
32
|
"adm-zip": "^0.5.16",
|
|
33
33
|
"copy-webpack-plugin": "^14.0.0",
|
|
34
34
|
"decompress": "^4.2.1",
|