@retinue/agentkit 0.2.0 → 0.3.1
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/README.md +1 -1
- package/dist/adapters/audio/openai.d.ts +36 -0
- package/dist/adapters/audio/openai.js +136 -0
- package/dist/adapters/bullmq/lock.d.ts +26 -2
- package/dist/adapters/memory/connections.d.ts +10 -0
- package/dist/adapters/memory/connections.js +87 -0
- package/dist/adapters/memory/graph.d.ts +17 -0
- package/dist/adapters/memory/graph.js +267 -0
- package/dist/adapters/memory/index.d.ts +3 -0
- package/dist/adapters/memory/index.js +3 -0
- package/dist/adapters/memory/rate-limit.d.ts +14 -0
- package/dist/adapters/memory/rate-limit.js +28 -0
- package/dist/adapters/memory/runtime.js +0 -0
- package/dist/adapters/postgres/connections.d.ts +15 -0
- package/dist/adapters/postgres/connections.js +134 -0
- package/dist/adapters/postgres/graph.d.ts +24 -0
- package/dist/adapters/postgres/graph.js +325 -0
- package/dist/adapters/postgres/index.d.ts +2 -0
- package/dist/adapters/postgres/index.js +2 -0
- package/dist/adapters/postgres/migrations.js +198 -0
- package/dist/adapters/postgres/run-store.js +26 -2
- package/dist/adapters/postgres/schema.d.ts +61 -2
- package/dist/adapters/postgres/schema.js +95 -10
- package/dist/adapters/redis/index.d.ts +1 -0
- package/dist/adapters/redis/index.js +1 -0
- package/dist/adapters/redis/rate-limit.d.ts +46 -0
- package/dist/adapters/redis/rate-limit.js +55 -0
- package/dist/adapters/supabase/index.d.ts +15 -0
- package/dist/adapters/supabase/index.js +15 -0
- package/dist/adapters/supabase/rls.js +33 -0
- package/dist/agents/agent.d.ts +78 -1
- package/dist/agents/agent.js +118 -5
- package/dist/agents/define.js +24 -4
- package/dist/agents/engine.d.ts +20 -0
- package/dist/agents/engine.js +177 -6
- package/dist/audio/index.d.ts +151 -0
- package/dist/audio/index.js +183 -0
- package/dist/connections/cipher.d.ts +103 -0
- package/dist/connections/cipher.js +141 -0
- package/dist/connections/index.d.ts +156 -0
- package/dist/connections/index.js +72 -0
- package/dist/connections/oauth/client.d.ts +84 -0
- package/dist/connections/oauth/client.js +141 -0
- package/dist/connections/oauth/index.d.ts +166 -0
- package/dist/connections/oauth/index.js +226 -0
- package/dist/connections/oauth/service.d.ts +90 -0
- package/dist/connections/oauth/service.js +132 -0
- package/dist/connections/pause.d.ts +68 -0
- package/dist/connections/pause.js +97 -0
- package/dist/connections/resolver.d.ts +59 -0
- package/dist/connections/resolver.js +138 -0
- package/dist/connections/resume.d.ts +64 -0
- package/dist/connections/resume.js +99 -0
- package/dist/connections/vault-cipher.d.ts +95 -0
- package/dist/connections/vault-cipher.js +206 -0
- package/dist/core/content-parts.d.ts +18 -2
- package/dist/core/content-parts.js +1 -0
- package/dist/core/context.d.ts +25 -0
- package/dist/core/errors.d.ts +1 -1
- package/dist/core/errors.js +15 -0
- package/dist/core/events.d.ts +44 -2
- package/dist/core/events.js +31 -2
- package/dist/core/validation.js +19 -0
- package/dist/entries/connections.d.ts +10 -0
- package/dist/entries/connections.js +10 -0
- package/dist/entries/knowledge.d.ts +8 -0
- package/dist/entries/knowledge.js +8 -0
- package/dist/entries/mcp-server.d.ts +9 -0
- package/dist/entries/mcp-server.js +9 -0
- package/dist/entries/runtime.d.ts +10 -0
- package/dist/entries/runtime.js +8 -0
- package/dist/entries/testing.d.ts +28 -0
- package/dist/entries/testing.js +28 -0
- package/dist/graphql/resolvers.d.ts +3 -1
- package/dist/graphql/resolvers.js +14 -0
- package/dist/knowledge/communities.d.ts +166 -0
- package/dist/knowledge/communities.js +377 -0
- package/dist/knowledge/graph-global.d.ts +153 -0
- package/dist/knowledge/graph-global.js +246 -0
- package/dist/knowledge/graph-retrieval.d.ts +150 -0
- package/dist/knowledge/graph-retrieval.js +303 -0
- package/dist/knowledge/graph.d.ts +223 -0
- package/dist/knowledge/graph.js +369 -0
- package/dist/knowledge/index.d.ts +24 -0
- package/dist/knowledge/index.js +36 -1
- package/dist/knowledge/retrieval.d.ts +26 -1
- package/dist/knowledge/retrieval.js +77 -1
- package/dist/mcp-server/index.d.ts +168 -0
- package/dist/mcp-server/index.js +175 -0
- package/dist/models/extraction.d.ts +73 -0
- package/dist/models/extraction.js +118 -0
- package/dist/models/index.d.ts +40 -1
- package/dist/models/index.js +22 -1
- package/dist/models/pricing.d.ts +2 -0
- package/dist/models/pricing.js +17 -1
- package/dist/models/provider-factory.js +9 -7
- package/dist/models/streaming.d.ts +89 -0
- package/dist/models/streaming.js +179 -2
- package/dist/persistence/index.d.ts +230 -0
- package/dist/persistence/index.js +2 -0
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +12 -0
- package/dist/runtime/worker.d.ts +36 -1
- package/dist/runtime/worker.js +37 -4
- package/dist/security/checklist.js +46 -0
- package/dist/server/bin.d.ts +72 -0
- package/dist/server/bin.js +304 -0
- package/dist/server/boot.d.ts +20 -2
- package/dist/server/boot.js +29 -3
- package/dist/server/cli-worker.js +6 -3
- package/dist/server/cli.d.ts +13 -0
- package/dist/server/cli.js +10 -5
- package/dist/server/config.d.ts +11 -1
- package/dist/server/config.js +18 -0
- package/dist/server/doctor.d.ts +85 -0
- package/dist/server/doctor.js +280 -0
- package/dist/server/pool.d.ts +47 -0
- package/dist/server/pool.js +73 -0
- package/dist/skills/context.d.ts +61 -0
- package/dist/skills/context.js +107 -0
- package/dist/skills/index.d.ts +1 -0
- package/dist/skills/index.js +1 -0
- package/dist/telemetry/spans.js +5 -0
- package/dist/testing/conformance/artifact-exports.d.ts +20 -0
- package/dist/testing/conformance/artifact-exports.js +172 -0
- package/dist/testing/conformance/artifacts.d.ts +11 -0
- package/dist/testing/conformance/artifacts.js +316 -0
- package/dist/testing/conformance/audio.d.ts +38 -0
- package/dist/testing/conformance/audio.js +145 -0
- package/dist/testing/conformance/capability.d.ts +23 -0
- package/dist/testing/conformance/capability.js +27 -0
- package/dist/testing/conformance/checkpoint-store.d.ts +15 -0
- package/dist/testing/conformance/checkpoint-store.js +72 -0
- package/dist/testing/conformance/connections.d.ts +14 -0
- package/dist/testing/conformance/connections.js +171 -0
- package/dist/testing/conformance/conversation-store.d.ts +10 -0
- package/dist/testing/conformance/conversation-store.js +60 -0
- package/dist/testing/conformance/evaluation.d.ts +10 -0
- package/dist/testing/conformance/evaluation.js +295 -0
- package/dist/testing/conformance/files.d.ts +19 -0
- package/dist/testing/conformance/files.js +454 -0
- package/dist/testing/conformance/flows.d.ts +16 -0
- package/dist/testing/conformance/flows.js +193 -0
- package/dist/testing/conformance/graph.d.ts +22 -0
- package/dist/testing/conformance/graph.js +500 -0
- package/dist/testing/conformance/hitl.d.ts +25 -0
- package/dist/testing/conformance/hitl.js +523 -0
- package/dist/testing/conformance/index.d.ts +163 -0
- package/dist/testing/conformance/index.js +321 -0
- package/dist/testing/conformance/invariants.d.ts +23 -0
- package/dist/testing/conformance/invariants.js +80 -0
- package/dist/testing/conformance/knowledge.d.ts +41 -0
- package/dist/testing/conformance/knowledge.js +592 -0
- package/dist/testing/conformance/parents.d.ts +50 -0
- package/dist/testing/conformance/parents.js +39 -0
- package/dist/testing/conformance/rate-limit.d.ts +25 -0
- package/dist/testing/conformance/rate-limit.js +71 -0
- package/dist/testing/conformance/records.d.ts +36 -0
- package/dist/testing/conformance/records.js +400 -0
- package/dist/testing/conformance/rollups.d.ts +30 -0
- package/dist/testing/conformance/rollups.js +425 -0
- package/dist/testing/conformance/run-coordinator.d.ts +27 -0
- package/dist/testing/conformance/run-coordinator.js +150 -0
- package/dist/testing/conformance/run-event-log.d.ts +9 -0
- package/dist/testing/conformance/run-event-log.js +138 -0
- package/dist/testing/conformance/run-store.d.ts +12 -0
- package/dist/testing/conformance/run-store.js +294 -0
- package/dist/testing/conformance/session-state.d.ts +38 -0
- package/dist/testing/conformance/session-state.js +198 -0
- package/dist/testing/conformance/usage-limits.d.ts +10 -0
- package/dist/testing/conformance/usage-limits.js +309 -0
- package/dist/testing/conformance.d.ts +8 -0
- package/dist/testing/conformance.js +8 -0
- package/dist/testing/memory-backend.d.ts +39 -0
- package/dist/testing/memory-backend.js +41 -0
- package/dist/testing/pglite.d.ts +55 -0
- package/dist/testing/pglite.js +78 -0
- package/dist/testing/stub-model.d.ts +58 -0
- package/dist/testing/stub-model.js +71 -0
- package/dist/testing/supabase-storage-double.d.ts +35 -0
- package/dist/testing/supabase-storage-double.js +101 -0
- package/dist/toolkit/http.d.ts +27 -0
- package/dist/toolkit/http.js +48 -0
- package/dist/toolkit/index.d.ts +27 -1
- package/dist/toolkit/index.js +24 -1
- package/dist/toolkit/mime.d.ts +97 -0
- package/dist/toolkit/mime.js +233 -0
- package/dist/toolkit/ssrf.d.ts +139 -0
- package/dist/toolkit/ssrf.js +349 -0
- package/dist/toolkit/vendor.d.ts +91 -0
- package/dist/toolkit/vendor.js +153 -0
- package/dist/tools/credentials.d.ts +265 -6
- package/dist/tools/credentials.js +318 -3
- package/dist/tools/define.d.ts +14 -0
- package/dist/tools/define.js +3 -0
- package/dist/tools/index.d.ts +21 -0
- package/dist/tools/index.js +7 -0
- package/dist/tools/library/audio.d.ts +63 -0
- package/dist/tools/library/audio.js +148 -0
- package/dist/tools/library/index.d.ts +8 -0
- package/dist/tools/library/index.js +7 -0
- package/dist/tools/registry.d.ts +33 -0
- package/dist/tools/registry.js +72 -2
- package/dist/usage/index.d.ts +21 -0
- package/dist/usage/index.js +1 -0
- package/dist/usage/rate-limit.d.ts +122 -0
- package/dist/usage/rate-limit.js +131 -0
- package/dist/usage/recorder.js +3 -0
- package/package.json +29 -2
package/dist/models/pricing.js
CHANGED
|
@@ -13,11 +13,27 @@
|
|
|
13
13
|
* Cached input is billed at the cache-read rate when present. This is what feeds usage accounting.
|
|
14
14
|
*/
|
|
15
15
|
export const computeModelCostMinorUnits = (pricing, usage) => {
|
|
16
|
+
/**
|
|
17
|
+
* Three kinds of input token, and all three are inside `inputTokens` — task #247.
|
|
18
|
+
*
|
|
19
|
+
* Measured against a live provider: `noCacheTokens + cacheReadTokens + cacheWriteTokens === inputTokens`. So
|
|
20
|
+
* fresh input is the remainder after both cache quantities, and adding them on top would double-bill — the
|
|
21
|
+
* same trap `nonTextInput` exists for.
|
|
22
|
+
*
|
|
23
|
+
* A cache **write** is not a discount. Anthropic charges 1.25× a fresh input token to write an entry, so
|
|
24
|
+
* folding writes into fresh input *under-bills* the first turn of every conversation — the direction that
|
|
25
|
+
* looks like a saving and is not. `cacheWritePerMillion` existed in `ModelPricing` and was read by nothing
|
|
26
|
+
* until now.
|
|
27
|
+
*/
|
|
16
28
|
const cachedIn = usage.cachedInputTokens ?? 0;
|
|
17
|
-
const
|
|
29
|
+
const cacheWrite = usage.cacheWriteTokens ?? 0;
|
|
30
|
+
const freshIn = Math.max(0, usage.inputTokens - cachedIn - cacheWrite);
|
|
18
31
|
const perMillion = (tokens, price) => (tokens * price) / 1_000_000;
|
|
19
32
|
const tokenCost = perMillion(freshIn, pricing.inputPerMillion) +
|
|
20
33
|
perMillion(cachedIn, pricing.cacheReadPerMillion ?? pricing.inputPerMillion) +
|
|
34
|
+
// Defaulting a write to the plain input rate is the conservative choice: it neither invents a premium a
|
|
35
|
+
// provider does not charge nor silently discounts one it does.
|
|
36
|
+
perMillion(cacheWrite, pricing.cacheWritePerMillion ?? pricing.inputPerMillion) +
|
|
21
37
|
perMillion(usage.outputTokens, pricing.outputPerMillion);
|
|
22
38
|
/**
|
|
23
39
|
* Non-text input, charged **only** where the provider prices it separately — #185 AC-4.
|
|
@@ -45,14 +45,16 @@ export const createProviderFactory = (config = {}) => {
|
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
47
|
return createOpenAICompatible({ name: c.name ?? "openai-compatible", baseURL: c.baseURL, apiKey: c.apiKey })(model.modelId);
|
|
48
|
-
case "bedrock":
|
|
49
|
-
// Bedrock wiring (@ai-sdk/amazon-bedrock + AWS credentials) is a follow-up.
|
|
50
|
-
throw new AgentPlatformError({
|
|
51
|
-
code: "capability_unavailable",
|
|
52
|
-
message: "Amazon Bedrock provider is not wired yet",
|
|
53
|
-
retryable: false,
|
|
54
|
-
});
|
|
55
48
|
default: {
|
|
49
|
+
/**
|
|
50
|
+
* Unreachable while `MODEL_PROVIDERS` and this switch agree — which is the point, and is now the
|
|
51
|
+
* *only* thing standing between a declared provider and a runtime failure.
|
|
52
|
+
*
|
|
53
|
+
* `"bedrock"` used to have a case here that threw. It was removed from the union in #256 rather than
|
|
54
|
+
* wired, because a declared provider that throws is worse than an absent one: it typechecks,
|
|
55
|
+
* satisfies this `never` assertion, and fails for whoever selects it first. `provider-coverage.test.ts`
|
|
56
|
+
* is what turns "mentioned in a switch" into "actually constructs".
|
|
57
|
+
*/
|
|
56
58
|
const exhaustive = model.provider;
|
|
57
59
|
throw new AgentPlatformError({
|
|
58
60
|
code: "capability_unavailable",
|
|
@@ -89,6 +89,14 @@ export type ModelTurnTool = {
|
|
|
89
89
|
/** Zod schema or JSON-schema object; a permissive object schema is used when absent. */
|
|
90
90
|
readonly inputSchema?: unknown;
|
|
91
91
|
execute(input: unknown, options?: ModelToolCallOptions): Promise<unknown>;
|
|
92
|
+
/**
|
|
93
|
+
* The tool's category, when the caller knows it — task #244.
|
|
94
|
+
*
|
|
95
|
+
* Optional and never sent to the provider. It exists so a caller can express a policy *about* categories —
|
|
96
|
+
* `AgentManifest.toolPolicy.categories` names the ones that must stay resident when the catalogue is bounded —
|
|
97
|
+
* without the engine having to reach back into the registry for a descriptor it was already handed.
|
|
98
|
+
*/
|
|
99
|
+
readonly category?: string;
|
|
92
100
|
};
|
|
93
101
|
export type ModelTurnRequest = {
|
|
94
102
|
readonly model: ResolvedModel;
|
|
@@ -116,6 +124,29 @@ export type ModelTurnRequest = {
|
|
|
116
124
|
* `temperature` matters for a second reason: the evaluation harness (#141) rests its reproducibility argument
|
|
117
125
|
* partly on temperature zero, and there was no way to ask for it on a real run.
|
|
118
126
|
*/
|
|
127
|
+
/**
|
|
128
|
+
* Ask the model for a value conforming to a schema, rather than prose — task #243.
|
|
129
|
+
*
|
|
130
|
+
* Neutral on purpose: this layer sits below `agents/`, so it takes a schema rather than an
|
|
131
|
+
* `AgentManifest["responseFormat"]`. The engine does the mapping.
|
|
132
|
+
*
|
|
133
|
+
* The schema must be validatable by this process — see `structuredValidator`. A bare JSON schema is refused,
|
|
134
|
+
* because the AI SDK's `jsonSchema()` wrapper leaves `validate` undefined: it constrains the provider request
|
|
135
|
+
* and checks nothing on the way back, which would make "structured" a request rather than a guarantee. That
|
|
136
|
+
* distinction is the entire point of the task.
|
|
137
|
+
*/
|
|
138
|
+
readonly structuredOutput?: {
|
|
139
|
+
readonly schema: unknown;
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Where this turn's prompt prefix may be cached, and how — task #247.
|
|
143
|
+
*
|
|
144
|
+
* `"explicit"` makes this layer emit the provider's cache directive; `"automatic"` and `"none"` emit nothing,
|
|
145
|
+
* for opposite reasons — one needs no help and the other would reject the field. Mapped by the engine from
|
|
146
|
+
* `ModelDefinition.capabilities.promptCaching`, so a caller that supplies no definition sends nothing, which
|
|
147
|
+
* is the behaviour every existing host already has.
|
|
148
|
+
*/
|
|
149
|
+
readonly promptCaching?: "automatic" | "explicit" | "none";
|
|
119
150
|
readonly maxOutputTokens?: number;
|
|
120
151
|
readonly temperature?: number;
|
|
121
152
|
readonly topP?: number;
|
|
@@ -124,7 +155,26 @@ export type ModelTurnRequest = {
|
|
|
124
155
|
export type NeutralUsage = {
|
|
125
156
|
readonly inputTokens: number;
|
|
126
157
|
readonly outputTokens: number;
|
|
158
|
+
/**
|
|
159
|
+
* Input tokens served from a prompt cache — a **subset** of `inputTokens`, not an addition to it.
|
|
160
|
+
*
|
|
161
|
+
* Read from the provider's `inputTokenDetails.cacheReadTokens` (task #247). It used to be read from
|
|
162
|
+
* `totalUsage.cachedInputTokens`, **a field the AI SDK does not send** — so this was zero on every turn, and
|
|
163
|
+
* `computeModelCostMinorUnits` billed cached tokens at the full input rate. Measured against a live model, a
|
|
164
|
+
* turn reusing a 9,700-token prefix reported 9,472 cache-read tokens and this platform recorded none of them.
|
|
165
|
+
*/
|
|
127
166
|
readonly cachedInputTokens: number;
|
|
167
|
+
/**
|
|
168
|
+
* Input tokens written *into* a prompt cache — also a subset of `inputTokens`.
|
|
169
|
+
*
|
|
170
|
+
* Its own quantity because it is priced differently and, on some providers, priced **higher** than a fresh
|
|
171
|
+
* input token: Anthropic charges 1.25× to write a cache entry. Folding it into fresh input under-bills a
|
|
172
|
+
* cache write and over-credits the first turn of every conversation, which is the direction that looks like a
|
|
173
|
+
* saving and is not.
|
|
174
|
+
*
|
|
175
|
+
* Absent means "not reported", not "none" — the rule `imageCount` already follows.
|
|
176
|
+
*/
|
|
177
|
+
readonly cacheWriteTokens?: number;
|
|
128
178
|
readonly reasoningTokens?: number;
|
|
129
179
|
/**
|
|
130
180
|
* Non-text input, counted from what **we sent** rather than from what the provider reported — #185 AC-4.
|
|
@@ -158,9 +208,48 @@ export type NeutralStreamChunk = {
|
|
|
158
208
|
} | {
|
|
159
209
|
readonly type: "finish";
|
|
160
210
|
readonly usage: NeutralUsage;
|
|
211
|
+
}
|
|
212
|
+
/** A validated structured answer — emitted once, at the end, only when `structuredOutput` was asked for. */
|
|
213
|
+
| {
|
|
214
|
+
readonly type: "structured-output";
|
|
215
|
+
readonly value: unknown;
|
|
161
216
|
} | {
|
|
162
217
|
readonly type: "error";
|
|
163
218
|
readonly error: unknown;
|
|
164
219
|
};
|
|
220
|
+
/**
|
|
221
|
+
* A schema this process can actually check, or a refusal — task #243.
|
|
222
|
+
*
|
|
223
|
+
* The rule: **only a schema with a validator is accepted for structured output.** Zod (a direct dependency) and
|
|
224
|
+
* anything else implementing Standard Schema qualify. A bare JSON-schema object does not, and refusing it is a
|
|
225
|
+
* deliberate choice rather than an omission:
|
|
226
|
+
*
|
|
227
|
+
* - The AI SDK's `jsonSchema()` wrapper returns `{ _type, jsonSchema, validate }` with **`validate` undefined**.
|
|
228
|
+
* It constrains the provider's generation and validates nothing coming back. Accepting one would mean the
|
|
229
|
+
* platform says "structured", the provider mostly complies, and nobody checks — which is a softer version of
|
|
230
|
+
* the bug being fixed, not a fix.
|
|
231
|
+
* - Validating JSON schema properly needs `ajv`, and that is a new runtime dependency for every consumer of a
|
|
232
|
+
* package whose entire dependency list is `ai` and `zod`. Not worth it when `z.object({...})` is one line and
|
|
233
|
+
* already validates.
|
|
234
|
+
*
|
|
235
|
+
* So this fails closed, at wiring time, with a message naming the fix. Tools keep taking JSON schema — a tool's
|
|
236
|
+
* arguments are validated by the provider and a bad call is a tool error the model can see and retry, which is
|
|
237
|
+
* a different situation from a guarantee made to a caller about a return value.
|
|
238
|
+
*/
|
|
239
|
+
export declare const structuredValidator: (schema: unknown) => ((value: unknown) => {
|
|
240
|
+
readonly ok: true;
|
|
241
|
+
} | {
|
|
242
|
+
readonly ok: false;
|
|
243
|
+
readonly detail: string;
|
|
244
|
+
});
|
|
245
|
+
/**
|
|
246
|
+
* The cache-read count, from wherever the provider put it — task #247.
|
|
247
|
+
*
|
|
248
|
+
* Exported so the arithmetic is testable without a provider: the whole defect this fixes was a field name that
|
|
249
|
+
* did not exist, which no amount of testing *through* a fake could reveal.
|
|
250
|
+
*/
|
|
251
|
+
export declare const cacheRead: (usage: Record<string, unknown>) => number;
|
|
252
|
+
/** The cache-write count, or `undefined` when the provider did not report one. */
|
|
253
|
+
export declare const cacheWrite: (usage: Record<string, unknown>) => number | undefined;
|
|
165
254
|
export declare function streamModelTurn(req: ModelTurnRequest): AsyncIterable<NeutralStreamChunk>;
|
|
166
255
|
//# sourceMappingURL=streaming.d.ts.map
|
package/dist/models/streaming.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* continues — and yields provider-neutral chunks. The agent engine consumes these chunks without
|
|
7
7
|
* ever importing the SDK, so switching providers changes nothing above this layer.
|
|
8
8
|
*/
|
|
9
|
-
import { jsonSchema, stepCountIs, streamText, tool as aiTool } from "ai";
|
|
9
|
+
import { Output, jsonSchema, stepCountIs, streamText, tool as aiTool } from "ai";
|
|
10
10
|
import { AgentPlatformError } from "../core/errors.js";
|
|
11
11
|
/** The text of a turn, for callers that count tokens or log. Non-text parts contribute nothing. */
|
|
12
12
|
export const turnText = (message) => typeof message.content === "string"
|
|
@@ -58,6 +58,62 @@ export const modalitiesOf = (messages) => {
|
|
|
58
58
|
}
|
|
59
59
|
return [...found];
|
|
60
60
|
};
|
|
61
|
+
/**
|
|
62
|
+
* A schema this process can actually check, or a refusal — task #243.
|
|
63
|
+
*
|
|
64
|
+
* The rule: **only a schema with a validator is accepted for structured output.** Zod (a direct dependency) and
|
|
65
|
+
* anything else implementing Standard Schema qualify. A bare JSON-schema object does not, and refusing it is a
|
|
66
|
+
* deliberate choice rather than an omission:
|
|
67
|
+
*
|
|
68
|
+
* - The AI SDK's `jsonSchema()` wrapper returns `{ _type, jsonSchema, validate }` with **`validate` undefined**.
|
|
69
|
+
* It constrains the provider's generation and validates nothing coming back. Accepting one would mean the
|
|
70
|
+
* platform says "structured", the provider mostly complies, and nobody checks — which is a softer version of
|
|
71
|
+
* the bug being fixed, not a fix.
|
|
72
|
+
* - Validating JSON schema properly needs `ajv`, and that is a new runtime dependency for every consumer of a
|
|
73
|
+
* package whose entire dependency list is `ai` and `zod`. Not worth it when `z.object({...})` is one line and
|
|
74
|
+
* already validates.
|
|
75
|
+
*
|
|
76
|
+
* So this fails closed, at wiring time, with a message naming the fix. Tools keep taking JSON schema — a tool's
|
|
77
|
+
* arguments are validated by the provider and a bad call is a tool error the model can see and retry, which is
|
|
78
|
+
* a different situation from a guarantee made to a caller about a return value.
|
|
79
|
+
*/
|
|
80
|
+
export const structuredValidator = (schema) => {
|
|
81
|
+
const standard = schema?.["~standard"];
|
|
82
|
+
if (schema !== null && typeof schema === "object" && typeof standard?.validate === "function") {
|
|
83
|
+
return (value) => {
|
|
84
|
+
const result = standard.validate(value);
|
|
85
|
+
// Standard Schema is allowed to return a promise; a validator that cannot answer synchronously is not
|
|
86
|
+
// usable here, and silently treating a pending promise as success is how this would pass having checked
|
|
87
|
+
// nothing. Refuse instead.
|
|
88
|
+
if (result instanceof Promise)
|
|
89
|
+
return { ok: false, detail: "the schema validates asynchronously, which this path cannot await" };
|
|
90
|
+
if (result.issues === undefined || result.issues.length === 0)
|
|
91
|
+
return { ok: true };
|
|
92
|
+
return {
|
|
93
|
+
ok: false,
|
|
94
|
+
detail: result.issues
|
|
95
|
+
.map((i) => `${(i.path ?? []).join(".") || "(root)"}: ${i.message ?? "invalid"}`)
|
|
96
|
+
.join("; "),
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (isZodSchema(schema)) {
|
|
101
|
+
const parse = schema
|
|
102
|
+
.safeParse;
|
|
103
|
+
return (value) => {
|
|
104
|
+
const result = parse.call(schema, value);
|
|
105
|
+
return result.success ? { ok: true } : { ok: false, detail: result.error?.message ?? "invalid" };
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
throw new AgentPlatformError({
|
|
109
|
+
code: "capability_unavailable",
|
|
110
|
+
message: "a structured response format needs a schema this process can validate — a Zod schema, or anything " +
|
|
111
|
+
"implementing Standard Schema. A plain JSON-schema object is refused: the AI SDK sends it to the " +
|
|
112
|
+
"provider but validates nothing on the way back, so the platform would be promising a shape it never " +
|
|
113
|
+
"checks. Use `z.object({ … })`.",
|
|
114
|
+
retryable: false,
|
|
115
|
+
});
|
|
116
|
+
};
|
|
61
117
|
const isZodSchema = (s) => typeof s === "object" && s !== null && typeof s.safeParse === "function";
|
|
62
118
|
/** A JSON-schema-shaped object: `{ type: "object", … }`. Enough to tell one from a Zod schema or from absence. */
|
|
63
119
|
const isJsonSchema = (s) => typeof s === "object" && s !== null && typeof s.type === "string";
|
|
@@ -148,6 +204,25 @@ const toModelContent = (content) => {
|
|
|
148
204
|
...(part.filename === undefined ? {} : { filename: part.filename }),
|
|
149
205
|
});
|
|
150
206
|
};
|
|
207
|
+
/**
|
|
208
|
+
* The cache-read count, from wherever the provider put it — task #247.
|
|
209
|
+
*
|
|
210
|
+
* Exported so the arithmetic is testable without a provider: the whole defect this fixes was a field name that
|
|
211
|
+
* did not exist, which no amount of testing *through* a fake could reveal.
|
|
212
|
+
*/
|
|
213
|
+
export const cacheRead = (usage) => {
|
|
214
|
+
const details = usage.inputTokenDetails;
|
|
215
|
+
if (details?.cacheReadTokens !== undefined)
|
|
216
|
+
return num(details.cacheReadTokens);
|
|
217
|
+
return num(usage.cachedInputTokens);
|
|
218
|
+
};
|
|
219
|
+
/** The cache-write count, or `undefined` when the provider did not report one. */
|
|
220
|
+
export const cacheWrite = (usage) => {
|
|
221
|
+
const details = usage.inputTokenDetails;
|
|
222
|
+
if (details?.cacheWriteTokens !== undefined)
|
|
223
|
+
return num(details.cacheWriteTokens);
|
|
224
|
+
return usage.cacheWriteTokens === undefined ? undefined : num(usage.cacheWriteTokens);
|
|
225
|
+
};
|
|
151
226
|
export async function* streamModelTurn(req) {
|
|
152
227
|
/**
|
|
153
228
|
* Refuse a modality the model cannot take — #185.
|
|
@@ -173,11 +248,52 @@ export async function* streamModelTurn(req) {
|
|
|
173
248
|
});
|
|
174
249
|
}
|
|
175
250
|
const messages = req.messages.map((m) => ({ role: m.role, content: toModelContent(m.content) }));
|
|
251
|
+
/**
|
|
252
|
+
* Validated before the call, not after — task #243 AC-2/AC-3.
|
|
253
|
+
*
|
|
254
|
+
* A schema this process cannot check makes "structured" a request rather than a guarantee, and finding that
|
|
255
|
+
* out after a paid generation is finding it out in the worst place. `structuredValidator` throws here.
|
|
256
|
+
*/
|
|
257
|
+
const validate = req.structuredOutput === undefined ? undefined : structuredValidator(req.structuredOutput.schema);
|
|
176
258
|
const result = streamText({
|
|
177
259
|
model: req.model,
|
|
178
260
|
...(req.system ? { system: req.system } : {}),
|
|
179
261
|
messages,
|
|
180
262
|
...(req.tools && req.tools.length > 0 ? { tools: toToolSet(req.tools) } : {}),
|
|
263
|
+
/**
|
|
264
|
+
* `output` rather than `streamObject`, because tools must keep working — AC-4.
|
|
265
|
+
*
|
|
266
|
+
* `streamObject` has no tool loop at all, so a structured agent would silently lose every tool. This keeps
|
|
267
|
+
* `streamText`'s model↔tool loop and constrains only the final answer.
|
|
268
|
+
*
|
|
269
|
+
* The option is `output`, **not** `experimental_output`. It was named the latter in `ai@4` and the name was
|
|
270
|
+
* dropped in `ai@7`; passing the old one is not an error, it is *ignored* — `streamText` accepts the unknown
|
|
271
|
+
* key, the model is never constrained, and `result.output` comes back as ordinary prose. Written down
|
|
272
|
+
* because that is the whole defect class this task exists to close, met again inside the fix: the first
|
|
273
|
+
* version of this code passed `experimental_output`, typechecked, and did nothing. Only the live check
|
|
274
|
+
* against a real model found it.
|
|
275
|
+
*/
|
|
276
|
+
...(req.structuredOutput === undefined
|
|
277
|
+
? {}
|
|
278
|
+
: { output: Output.object({ schema: req.structuredOutput.schema }) }),
|
|
279
|
+
/**
|
|
280
|
+
* The cache breakpoint, for providers that need one told — task #247.
|
|
281
|
+
*
|
|
282
|
+
* Only `"explicit"`. Anthropic caches nothing unless a block carries `cache_control`, so a platform that
|
|
283
|
+
* emitted nothing got no caching there at all — which is what this did. OpenAI needs no directive and would
|
|
284
|
+
* treat one as an unknown field, so `"automatic"` deliberately sends nothing.
|
|
285
|
+
*
|
|
286
|
+
* The breakpoint goes on the **system** block, which is where the stable prefix is: the system prompt and
|
|
287
|
+
* the tool catalogue are byte-identical across every turn of a conversation, and the history after them is
|
|
288
|
+
* not. Anthropic caches everything *up to* a breakpoint, so marking the system block caches the prompt and
|
|
289
|
+
* the tool definitions together.
|
|
290
|
+
*
|
|
291
|
+
* `providerOptions` rather than a top-level field, because this is provider-specific by construction and the
|
|
292
|
+
* AI SDK's neutral surface has no cache concept. A provider that ignores the namespace is unaffected.
|
|
293
|
+
*/
|
|
294
|
+
...(req.promptCaching === "explicit"
|
|
295
|
+
? { providerOptions: { anthropic: { cacheControl: { type: "ephemeral" } } } }
|
|
296
|
+
: {}),
|
|
181
297
|
stopWhen: stepCountIs(req.maxSteps ?? 8),
|
|
182
298
|
...(req.abortSignal ? { abortSignal: req.abortSignal } : {}),
|
|
183
299
|
// Spread conditionally so an unset parameter leaves the provider's own default alone, rather than pinning it
|
|
@@ -190,6 +306,15 @@ export async function* streamModelTurn(req) {
|
|
|
190
306
|
for await (const chunk of result.fullStream) {
|
|
191
307
|
switch (chunk.type) {
|
|
192
308
|
case "text-delta":
|
|
309
|
+
/**
|
|
310
|
+
* Swallowed on a structured turn — AC-5.
|
|
311
|
+
*
|
|
312
|
+
* With `experimental_output` the model's text *is* the JSON, arriving a fragment at a time. Forwarding it
|
|
313
|
+
* would put half-built JSON in the transcript as prose and leave a reader watching `{"na` appear. The
|
|
314
|
+
* decision is one complete part at the end instead; tool calls still stream, so the turn is not silent.
|
|
315
|
+
*/
|
|
316
|
+
if (req.structuredOutput !== undefined)
|
|
317
|
+
break;
|
|
193
318
|
yield { type: "text-delta", id: chunk.id ?? "text", text: chunk.text ?? "" };
|
|
194
319
|
break;
|
|
195
320
|
case "tool-call":
|
|
@@ -249,7 +374,20 @@ export async function* streamModelTurn(req) {
|
|
|
249
374
|
usage: {
|
|
250
375
|
inputTokens: num(usage.inputTokens),
|
|
251
376
|
outputTokens: num(usage.outputTokens),
|
|
252
|
-
|
|
377
|
+
/**
|
|
378
|
+
* From `inputTokenDetails`, with the old field as a fallback — task #247.
|
|
379
|
+
*
|
|
380
|
+
* The AI SDK reports the breakdown as
|
|
381
|
+
* `inputTokenDetails: { noCacheTokens, cacheReadTokens, cacheWriteTokens }`, and **not** as
|
|
382
|
+
* `cachedInputTokens`. Reading the latter — which is what this did — yielded `undefined` on every
|
|
383
|
+
* provider, so `num()` made it zero and every cached token was billed at the full input rate.
|
|
384
|
+
*
|
|
385
|
+
* The fallback is kept because the field is what a *host-supplied* `streamTurn` would most naturally
|
|
386
|
+
* set, and because a future SDK may reinstate it. Order matters: the detailed breakdown wins, since
|
|
387
|
+
* it is the one a real provider fills in.
|
|
388
|
+
*/
|
|
389
|
+
cachedInputTokens: cacheRead(usage),
|
|
390
|
+
...(cacheWrite(usage) === undefined ? {} : { cacheWriteTokens: cacheWrite(usage) }),
|
|
253
391
|
...(usage.reasoningTokens !== undefined ? { reasoningTokens: num(usage.reasoningTokens) } : {}),
|
|
254
392
|
/**
|
|
255
393
|
* Non-text input, counted from the request rather than read from the response — #185 AC-4.
|
|
@@ -272,5 +410,44 @@ export async function* streamModelTurn(req) {
|
|
|
272
410
|
break;
|
|
273
411
|
}
|
|
274
412
|
}
|
|
413
|
+
if (validate === undefined)
|
|
414
|
+
return;
|
|
415
|
+
/**
|
|
416
|
+
* The structured answer, after the loop and after validation — AC-2.
|
|
417
|
+
*
|
|
418
|
+
* Read from the SDK's resolved output rather than reassembled from the text deltas that were swallowed above:
|
|
419
|
+
* the SDK has already parsed the JSON, and re-parsing a string this layer discarded would be two chances to
|
|
420
|
+
* get it wrong.
|
|
421
|
+
*
|
|
422
|
+
* Every failure here is a **run failure**, deliberately. The alternative is emitting the text as an ordinary
|
|
423
|
+
* answer, which is precisely the defect this task fixes: an agent that asked for a schema, got prose, and had
|
|
424
|
+
* no way to tell. A caller who wanted best-effort prose did not set a structured response format.
|
|
425
|
+
*/
|
|
426
|
+
let value;
|
|
427
|
+
try {
|
|
428
|
+
value = await result.output;
|
|
429
|
+
}
|
|
430
|
+
catch (thrown) {
|
|
431
|
+
throw new AgentPlatformError({
|
|
432
|
+
code: "provider_error",
|
|
433
|
+
message: "the model produced no value conforming to the structured response format: " +
|
|
434
|
+
errorMessageOf(thrown) +
|
|
435
|
+
". The turn is failed rather than returning the raw text, which would be prose presented as a " +
|
|
436
|
+
"validated object.",
|
|
437
|
+
retryable: true,
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
// Belt and braces over the SDK's own parse. `Output.object` validates a Standard Schema, but this layer is
|
|
441
|
+
// where the guarantee is made, and a guarantee that depends on a dependency's internals is a guarantee that
|
|
442
|
+
// changes when the dependency does.
|
|
443
|
+
const verdict = validate(value);
|
|
444
|
+
if (!verdict.ok)
|
|
445
|
+
throw new AgentPlatformError({
|
|
446
|
+
code: "provider_error",
|
|
447
|
+
message: `the model's answer does not satisfy the structured response format — ${verdict.detail}. ` +
|
|
448
|
+
"The turn is failed rather than returning it unchecked.",
|
|
449
|
+
retryable: true,
|
|
450
|
+
});
|
|
451
|
+
yield { type: "structured-output", value };
|
|
275
452
|
}
|
|
276
453
|
//# sourceMappingURL=streaming.js.map
|
|
@@ -237,7 +237,36 @@ export interface RunStore {
|
|
|
237
237
|
workerId: string;
|
|
238
238
|
leaseMs: number;
|
|
239
239
|
now: string;
|
|
240
|
+
/**
|
|
241
|
+
* Refuse the claim when the tenant already holds this many **live leases** — REQ-058 (#246), task #265.
|
|
242
|
+
*
|
|
243
|
+
* Enforced *here*, inside the claim, and that placement is the whole design. Counting live leases and
|
|
244
|
+
* then claiming in a second statement is check-then-act: two workers in two processes both read
|
|
245
|
+
* "3 of 4 used" and both claim, and the tenant runs at 5. An adapter must make this one atomic
|
|
246
|
+
* operation — one SQL statement, or one turn of the event loop for the reference adapter.
|
|
247
|
+
*
|
|
248
|
+
* **Live leases, not a counter.** A counter incremented at admission and decremented at completion leaks
|
|
249
|
+
* a unit every time a worker dies mid-run, and a leaked unit is invisible until the tenant's effective
|
|
250
|
+
* concurrency has silently reached zero. A lease expires on its own, so a crashed worker's slot returns
|
|
251
|
+
* without anybody noticing it was gone.
|
|
252
|
+
*
|
|
253
|
+
* The run being claimed is never counted against itself, so recovering an expired lease is not blocked
|
|
254
|
+
* by the limit it is trying to satisfy.
|
|
255
|
+
*
|
|
256
|
+
* Absent or non-positive means unlimited.
|
|
257
|
+
*/
|
|
258
|
+
maxConcurrent?: number;
|
|
240
259
|
}): Promise<Run | null>;
|
|
260
|
+
/**
|
|
261
|
+
* How many of a tenant's runs hold a live lease right now.
|
|
262
|
+
*
|
|
263
|
+
* **Not the enforcement path** — `claim` is, because only `claim` can be atomic with the decision. This
|
|
264
|
+
* exists for two honest uses: telling an operator why a tenant's work is waiting, and letting the worker
|
|
265
|
+
* label a refused claim. A slightly stale answer costs a wrong log line, never a wrong admission.
|
|
266
|
+
*/
|
|
267
|
+
countLive(input: TenantScope & {
|
|
268
|
+
now: string;
|
|
269
|
+
}): Promise<number>;
|
|
241
270
|
/** Extend the lease. Returns false when the claim was lost (reaped/stolen) so the worker aborts. */
|
|
242
271
|
keepalive(input: TenantScope & {
|
|
243
272
|
id: RunId;
|
|
@@ -1524,6 +1553,207 @@ export interface KeywordIndex {
|
|
|
1524
1553
|
sourceTypes?: readonly KnowledgeSourceType[];
|
|
1525
1554
|
}): Promise<readonly KeywordSearchHit[]>;
|
|
1526
1555
|
}
|
|
1556
|
+
/**
|
|
1557
|
+
* Entities and relationships extracted from chunks, so retrieval can answer what no chunk says.
|
|
1558
|
+
*
|
|
1559
|
+
* A fourth port beside `KnowledgeStore`, `VectorIndex` and `KeywordIndex`, for the same reason those are three
|
|
1560
|
+
* rather than one: it answers a different question and can be backed by a different system. A deployment on
|
|
1561
|
+
* Postgres satisfies all four with one database; one that later wants a graph database swaps this alone.
|
|
1562
|
+
*
|
|
1563
|
+
* **Provenance is structural, not decorative.** Every entity and every edge records the chunk ids it was
|
|
1564
|
+
* extracted from, and a row without provenance cannot be written. The retriever surfaces graph-derived material
|
|
1565
|
+
* as *citable* hits, so a claim with no traceable chunk is one the model would present as though a document
|
|
1566
|
+
* said it. `SEMANTIC_RELEVANCE_FLOOR` exists so retrieval can say "nothing"; this exists so the graph cannot say
|
|
1567
|
+
* "something" without saying where from.
|
|
1568
|
+
*
|
|
1569
|
+
* **A source's contribution is what gets replaced, not the entity.** An entity like "retry budget" is mentioned
|
|
1570
|
+
* by many sources, so re-indexing one document must not delete it — it must withdraw *that document's* claims
|
|
1571
|
+
* and leave the rest. Hence the split: contributions are per source and per chunk, and an entity exists exactly
|
|
1572
|
+
* as long as some contribution still names it. Deleting a source therefore prunes the entities only that source
|
|
1573
|
+
* knew about, automatically, with no reference counting for anybody to get wrong.
|
|
1574
|
+
*/
|
|
1575
|
+
/** An entity's type as the extractor named it — free text, lowercased. Not a union: a corpus decides its own. */
|
|
1576
|
+
export type KnowledgeEntity = {
|
|
1577
|
+
/** Deterministic and readable: `type:normalised-name`. See `entityId` in `knowledge/graph.ts`. */
|
|
1578
|
+
readonly id: string;
|
|
1579
|
+
/** The canonical surface form — the one chosen deterministically from everything merged into it. */
|
|
1580
|
+
readonly name: string;
|
|
1581
|
+
readonly type: string;
|
|
1582
|
+
readonly description?: string;
|
|
1583
|
+
/**
|
|
1584
|
+
* Every distinct spelling merged into this entity, sorted — AC-6.
|
|
1585
|
+
*
|
|
1586
|
+
* Resolution merges things that are not the same, and the mistake is invisible unless the merges are
|
|
1587
|
+
* recorded. Storing the surface forms turns "why is this entity called that" from an archaeology problem
|
|
1588
|
+
* into a lookup.
|
|
1589
|
+
*/
|
|
1590
|
+
readonly surfaceForms: readonly string[];
|
|
1591
|
+
/** Chunk ids this entity was extracted from, sorted. Never empty. */
|
|
1592
|
+
readonly provenance: readonly string[];
|
|
1593
|
+
};
|
|
1594
|
+
export type KnowledgeRelationship = {
|
|
1595
|
+
/** Deterministic: `fromId|type|toId`. */
|
|
1596
|
+
readonly id: string;
|
|
1597
|
+
readonly fromId: string;
|
|
1598
|
+
readonly toId: string;
|
|
1599
|
+
readonly type: string;
|
|
1600
|
+
readonly description?: string;
|
|
1601
|
+
/** How many chunks asserted this edge. The obvious traversal ordering, and it needs no calibration. */
|
|
1602
|
+
readonly weight: number;
|
|
1603
|
+
/** Chunk ids this edge was extracted from, sorted. Never empty. */
|
|
1604
|
+
readonly provenance: readonly string[];
|
|
1605
|
+
};
|
|
1606
|
+
/** What one source contributed, as written. The store merges these into entities and edges. */
|
|
1607
|
+
/**
|
|
1608
|
+
* A community as stored: the cluster, and whatever summary has been written for it.
|
|
1609
|
+
*
|
|
1610
|
+
* `summaryFingerprint` is the mechanism behind visible staleness. A summary records the membership it was
|
|
1611
|
+
* written against; when the graph changes and the community's `fingerprint` moves on, the two differ and the
|
|
1612
|
+
* summary is knowably out of date — without a timestamp anybody has to interpret, and without re-reading the
|
|
1613
|
+
* summary to guess.
|
|
1614
|
+
*/
|
|
1615
|
+
export type StoredCommunity = {
|
|
1616
|
+
readonly id: string;
|
|
1617
|
+
readonly level: number;
|
|
1618
|
+
readonly entityIds: readonly string[];
|
|
1619
|
+
readonly relationshipIds: readonly string[];
|
|
1620
|
+
readonly chunkIds: readonly string[];
|
|
1621
|
+
readonly fingerprint: string;
|
|
1622
|
+
readonly summary?: string;
|
|
1623
|
+
/** The `fingerprint` the summary was written against. Differs from `fingerprint` exactly when stale. */
|
|
1624
|
+
readonly summaryFingerprint?: string;
|
|
1625
|
+
readonly summarisedAt?: string;
|
|
1626
|
+
};
|
|
1627
|
+
/** True when a community has a summary that no longer describes its membership. */
|
|
1628
|
+
export declare const isCommunityStale: (community: StoredCommunity) => boolean;
|
|
1629
|
+
export type GraphContribution = {
|
|
1630
|
+
readonly entities: readonly KnowledgeEntity[];
|
|
1631
|
+
readonly relationships: readonly KnowledgeRelationship[];
|
|
1632
|
+
};
|
|
1633
|
+
/**
|
|
1634
|
+
* Whether this tenant uses the graph at all, and which of its sources do.
|
|
1635
|
+
*
|
|
1636
|
+
* Two levels, because the cost is per chunk and paid at index time. A tenant switch alone would mean enabling
|
|
1637
|
+
* the feature silently multiplies every tenant's indexing bill — including for the ad-hoc PDF somebody attached
|
|
1638
|
+
* to one conversation. A per-source flag alone would leave no single place to say "this deployment does not use
|
|
1639
|
+
* GraphRAG", which is the guarantee AC-1 rests on.
|
|
1640
|
+
*
|
|
1641
|
+
* A source flag is stored and honoured **independently** of the tenant switch, so a deployment can mark its
|
|
1642
|
+
* handbook today and enable the tenant next week without re-marking anything.
|
|
1643
|
+
*/
|
|
1644
|
+
export type GraphSettings = {
|
|
1645
|
+
readonly enabled: boolean;
|
|
1646
|
+
/** When it was last changed, for an operator wondering why a bill moved. */
|
|
1647
|
+
readonly updatedAt: string;
|
|
1648
|
+
};
|
|
1649
|
+
export interface GraphStore {
|
|
1650
|
+
getSettings(input: TenantScope): Promise<GraphSettings>;
|
|
1651
|
+
setEnabled(input: TenantScope & {
|
|
1652
|
+
enabled: boolean;
|
|
1653
|
+
at: string;
|
|
1654
|
+
}): Promise<GraphSettings>;
|
|
1655
|
+
/** Marks one source. Independent of the tenant switch — see `GraphSettings`. */
|
|
1656
|
+
setSourceEnabled(input: TenantScope & {
|
|
1657
|
+
sourceType: KnowledgeSourceType;
|
|
1658
|
+
sourceId: string;
|
|
1659
|
+
enabled: boolean;
|
|
1660
|
+
}): Promise<void>;
|
|
1661
|
+
isSourceEnabled(input: TenantScope & {
|
|
1662
|
+
sourceType: KnowledgeSourceType;
|
|
1663
|
+
sourceId: string;
|
|
1664
|
+
}): Promise<boolean>;
|
|
1665
|
+
listEnabledSources(input: TenantScope & PageRequest): Promise<Page<{
|
|
1666
|
+
readonly sourceType: KnowledgeSourceType;
|
|
1667
|
+
readonly sourceId: string;
|
|
1668
|
+
}>>;
|
|
1669
|
+
/**
|
|
1670
|
+
* Replaces one source's contribution to the graph.
|
|
1671
|
+
*
|
|
1672
|
+
* Replace rather than append, for the reason `replaceSource` is: re-indexing a changed document must not
|
|
1673
|
+
* leave its old claims in the graph. Entities and edges no source names any more are pruned by this call.
|
|
1674
|
+
*
|
|
1675
|
+
* Refuses a row with empty provenance rather than storing an untraceable claim.
|
|
1676
|
+
*/
|
|
1677
|
+
replaceSourceGraph(input: TenantScope & {
|
|
1678
|
+
sourceType: KnowledgeSourceType;
|
|
1679
|
+
sourceId: string;
|
|
1680
|
+
contribution: GraphContribution;
|
|
1681
|
+
}): Promise<{
|
|
1682
|
+
readonly entities: number;
|
|
1683
|
+
readonly relationships: number;
|
|
1684
|
+
readonly pruned: number;
|
|
1685
|
+
}>;
|
|
1686
|
+
deleteSourceGraph(input: TenantScope & {
|
|
1687
|
+
sourceType: KnowledgeSourceType;
|
|
1688
|
+
sourceId: string;
|
|
1689
|
+
}): Promise<{
|
|
1690
|
+
readonly pruned: number;
|
|
1691
|
+
}>;
|
|
1692
|
+
getEntity(input: TenantScope & {
|
|
1693
|
+
id: string;
|
|
1694
|
+
}): Promise<KnowledgeEntity | null>;
|
|
1695
|
+
/** Entities by exact id, for a query-side resolver that has already normalised. */
|
|
1696
|
+
getEntities(input: TenantScope & {
|
|
1697
|
+
ids: readonly string[];
|
|
1698
|
+
}): Promise<readonly KnowledgeEntity[]>;
|
|
1699
|
+
/**
|
|
1700
|
+
* Entities whose **normalised name** matches, across every type — REQ-064 (#270), task #273.
|
|
1701
|
+
*
|
|
1702
|
+
* A query says "the retry budget" and the graph holds `concept:retry budget`; the type is not in the
|
|
1703
|
+
* question and cannot be. So resolution is by name, and the caller supplies names already normalised by
|
|
1704
|
+
* `normaliseName`.
|
|
1705
|
+
*
|
|
1706
|
+
* That the id *is* `type:normalisedName` is what makes this a suffix match rather than a second stored
|
|
1707
|
+
* column, and it is why query-side and index-side resolution agree by construction: the id was built by the
|
|
1708
|
+
* same function the caller just called. A separate `normalised_name` column would be a second copy to drift.
|
|
1709
|
+
*/
|
|
1710
|
+
resolveEntities(input: TenantScope & {
|
|
1711
|
+
normalisedNames: readonly string[];
|
|
1712
|
+
}): Promise<readonly KnowledgeEntity[]>;
|
|
1713
|
+
listEntities(input: TenantScope & PageRequest & {
|
|
1714
|
+
type?: string;
|
|
1715
|
+
}): Promise<Page<KnowledgeEntity>>;
|
|
1716
|
+
/** Every edge touching any of `entityIds`, in either direction. The traversal primitive. */
|
|
1717
|
+
neighbours(input: TenantScope & {
|
|
1718
|
+
entityIds: readonly string[];
|
|
1719
|
+
limit: number;
|
|
1720
|
+
}): Promise<readonly KnowledgeRelationship[]>;
|
|
1721
|
+
/**
|
|
1722
|
+
* Replaces the whole community hierarchy — REQ-064 (#270), task #272.
|
|
1723
|
+
*
|
|
1724
|
+
* Whole rather than per level, because clustering is global: an entity moving between communities can change
|
|
1725
|
+
* every level above it, and a partial write would leave a hierarchy whose levels disagree about who is in
|
|
1726
|
+
* what. Summaries already written are carried over for communities whose fingerprint is unchanged, which is
|
|
1727
|
+
* what makes re-summarisation incremental — the expensive half — while clustering stays global and cheap.
|
|
1728
|
+
*/
|
|
1729
|
+
replaceCommunities(input: TenantScope & {
|
|
1730
|
+
communities: readonly StoredCommunity[];
|
|
1731
|
+
}): Promise<{
|
|
1732
|
+
readonly written: number;
|
|
1733
|
+
readonly summariesKept: number;
|
|
1734
|
+
}>;
|
|
1735
|
+
listCommunities(input: TenantScope & PageRequest & {
|
|
1736
|
+
level?: number;
|
|
1737
|
+
}): Promise<Page<StoredCommunity>>;
|
|
1738
|
+
getCommunity(input: TenantScope & {
|
|
1739
|
+
id: string;
|
|
1740
|
+
}): Promise<StoredCommunity | null>;
|
|
1741
|
+
/** Records a generated summary against the fingerprint it was written for. */
|
|
1742
|
+
setCommunitySummary(input: TenantScope & {
|
|
1743
|
+
id: string;
|
|
1744
|
+
summary: string;
|
|
1745
|
+
fingerprint: string;
|
|
1746
|
+
at: string;
|
|
1747
|
+
}): Promise<void>;
|
|
1748
|
+
/**
|
|
1749
|
+
* A stable serialisation of the whole graph, for the determinism assertion — AC-6.
|
|
1750
|
+
*
|
|
1751
|
+
* On the port rather than built by the test, so *every* adapter is held to it. A test that serialised the
|
|
1752
|
+
* graph itself would prove the reference adapter deterministic and say nothing about Postgres, where row
|
|
1753
|
+
* order is the thing most likely to differ.
|
|
1754
|
+
*/
|
|
1755
|
+
fingerprint(input: TenantScope): Promise<string>;
|
|
1756
|
+
}
|
|
1527
1757
|
/**
|
|
1528
1758
|
* Content-addressable blob storage for spilled tool output (`docs/03` → Tool results). A large
|
|
1529
1759
|
* result is offloaded here and referenced by an authorized `BlobRef`, read back via
|
|
@@ -214,6 +214,8 @@ export const KEYWORD_STOPWORDS = new Set([
|
|
|
214
214
|
export const stripStopwords = (query) => (query.toLowerCase().match(/[a-z0-9]+(?:-[a-z0-9]+)*/g) ?? [])
|
|
215
215
|
.filter((term) => !KEYWORD_STOPWORDS.has(term))
|
|
216
216
|
.join(" ");
|
|
217
|
+
/** True when a community has a summary that no longer describes its membership. */
|
|
218
|
+
export const isCommunityStale = (community) => community.summary !== undefined && community.summaryFingerprint !== community.fingerprint;
|
|
217
219
|
export const ADAPTER_CAPABILITIES = [
|
|
218
220
|
"transactions",
|
|
219
221
|
"row-level-security",
|