@squidcloud/cli 1.0.487 → 1.0.489
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 +69 -19
- 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 +372 -10
- 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 +75 -4
- package/dist/resources/claude/skills/squid-development/reference/console.md +6 -1
- package/dist/resources/claude/skills/squid-development/reference/databases.md +48 -0
- package/dist/resources/claude/skills/squid-development/reference/openai.md +1 -1
- 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/dist/resources/claude/skills/squid-react-development/SKILL.md +21 -4
- package/package.json +2 -2
|
@@ -54,10 +54,25 @@ 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.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.7-flash`, `gemini-3.5-flash-lite` |
|
|
65
|
+
| Grok | `grok-4.6`, `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)
|
|
60
|
-
- **Image Generation Models**: Used for image creation (
|
|
75
|
+
- **Image Generation Models**: Used for image creation (OpenAI `gpt-image-*`, Stable Diffusion, Flux)
|
|
61
76
|
- **Audio Models**: Transcription (Whisper, GPT-4o) and Text-to-Speech (TTS-1, GPT-4o-mini-tts)
|
|
62
77
|
|
|
63
78
|
## AI Agents
|
|
@@ -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-
|
|
118
|
+
model: 'gpt-5.6-terra', // or 'claude-sonnet-5', 'gemini-3.7-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-
|
|
389
|
+
model: 'gpt-5.6-terra', // 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-
|
|
436
|
+
model: 'gpt-5.6-terra',
|
|
324
437
|
reasoningEffort: 'high',
|
|
325
438
|
allowClarificationQuestions: false
|
|
326
439
|
},
|
|
@@ -456,14 +569,221 @@ const context = await kb.getContext('doc-123');
|
|
|
456
569
|
const allContexts = await kb.listContexts(1000); // truncateTextAfter
|
|
457
570
|
const contextIds = await kb.listContextIds();
|
|
458
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
|
+
|
|
459
580
|
// Download context
|
|
460
581
|
const download = await kb.downloadContext('doc-123');
|
|
461
582
|
|
|
462
583
|
// Delete contexts
|
|
463
584
|
await kb.deleteContext('doc-123');
|
|
464
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 });
|
|
725
|
+
```
|
|
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, /* ... */ })));
|
|
465
773
|
```
|
|
466
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
|
+
|
|
467
787
|
### Spreadsheet Understanding
|
|
468
788
|
|
|
469
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.
|
|
@@ -498,10 +818,10 @@ REST API: [AI Image API](https://docs.getsquid.ai/reference-docs/api/#tag/AI-Ima
|
|
|
498
818
|
### Image Generation
|
|
499
819
|
|
|
500
820
|
```typescript
|
|
501
|
-
// Generate image
|
|
821
|
+
// Generate image - `modelName` is required
|
|
502
822
|
const imageUrl = await squid.ai().image().generate(
|
|
503
823
|
'A futuristic city',
|
|
504
|
-
{ size: '1024x1024', quality: '
|
|
824
|
+
{ modelName: 'gpt-image-1', size: '1024x1024', quality: 'high' }
|
|
505
825
|
);
|
|
506
826
|
|
|
507
827
|
// Remove background
|
|
@@ -574,7 +894,7 @@ const settings = await aiClient.getApplicationAiSettings();
|
|
|
574
894
|
|
|
575
895
|
// Set application AI settings
|
|
576
896
|
await aiClient.setApplicationAiSettings({
|
|
577
|
-
defaultModel: 'gpt-5-
|
|
897
|
+
defaultModel: 'gpt-5.6-terra',
|
|
578
898
|
// ... other settings
|
|
579
899
|
});
|
|
580
900
|
|
|
@@ -583,8 +903,20 @@ await aiClient.setAiProviderApiKeySecret(
|
|
|
583
903
|
'openai', // providerType
|
|
584
904
|
'OPENAI_API_KEY' // secret key name
|
|
585
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();
|
|
586
914
|
```
|
|
587
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
|
+
|
|
588
920
|
## Backend Decorators
|
|
589
921
|
|
|
590
922
|
### @aiFunction
|
|
@@ -640,6 +972,36 @@ async bookHotel(args: BookingArgs): Promise<string> {
|
|
|
640
972
|
|
|
641
973
|
For `@secureAiAgent` and `@secureAiQuery`, see [security.md](security.md#ai-security).
|
|
642
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
|
+
|
|
643
1005
|
## MCP (Model Context Protocol)
|
|
644
1006
|
|
|
645
1007
|
Squid supports MCP for extending agent capabilities.
|
|
@@ -787,7 +1149,7 @@ configureAiFunctions(request: AiFunctionsConfiguratorRequest): AiFunctionsConfig
|
|
|
787
1149
|
## Best Practices
|
|
788
1150
|
|
|
789
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.
|
|
790
|
-
2. **Use memoryOptions for AI conversations** -
|
|
1152
|
+
2. **Use memoryOptions for AI conversations** - `memoryOptions.memoryId` names the conversation, `memoryOptions.memoryMode` controls history
|
|
791
1153
|
3. **Agent IDs are permanent** - Plan naming carefully, cannot be changed after creation
|
|
792
1154
|
4. **Memory is enabled by default** - Set `memoryMode: 'none'` to disable conversation history
|
|
793
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.
|