@cogenta/agents 0.2.1 → 0.5.0
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/agents/builtins.d.ts +51 -0
- package/dist/agents/builtins.d.ts.map +1 -0
- package/dist/agents/builtins.js +190 -0
- package/dist/agents/builtins.js.map +1 -0
- package/dist/agents/orchestrator.d.ts +98 -0
- package/dist/agents/orchestrator.d.ts.map +1 -0
- package/dist/agents/orchestrator.js +286 -0
- package/dist/agents/orchestrator.js.map +1 -0
- package/dist/agents/store.d.ts +71 -0
- package/dist/agents/store.d.ts.map +1 -0
- package/dist/agents/store.js +211 -0
- package/dist/agents/store.js.map +1 -0
- package/dist/agents/types.d.ts +10 -0
- package/dist/agents/types.d.ts.map +1 -1
- package/dist/assist/agent-identity.d.ts +44 -0
- package/dist/assist/agent-identity.d.ts.map +1 -0
- package/dist/assist/agent-identity.js +136 -0
- package/dist/assist/agent-identity.js.map +1 -0
- package/dist/assist/chat.d.ts +2 -0
- package/dist/assist/chat.d.ts.map +1 -1
- package/dist/assist/chat.js +14 -5
- package/dist/assist/chat.js.map +1 -1
- package/dist/assist/classify.d.ts +3 -2
- package/dist/assist/classify.d.ts.map +1 -1
- package/dist/assist/classify.js +30 -11
- package/dist/assist/classify.js.map +1 -1
- package/dist/assist/faq.d.ts +3 -2
- package/dist/assist/faq.d.ts.map +1 -1
- package/dist/assist/faq.js +31 -13
- package/dist/assist/faq.js.map +1 -1
- package/dist/assist/runtime.d.ts +8 -2
- package/dist/assist/runtime.d.ts.map +1 -1
- package/dist/assist/runtime.js +2 -2
- package/dist/assist/runtime.js.map +1 -1
- package/dist/assist/toolset.d.ts +10 -0
- package/dist/assist/toolset.d.ts.map +1 -1
- package/dist/assist/toolset.js +36 -8
- package/dist/assist/toolset.js.map +1 -1
- package/dist/assist/usage.d.ts +58 -0
- package/dist/assist/usage.d.ts.map +1 -0
- package/dist/assist/usage.js +53 -0
- package/dist/assist/usage.js.map +1 -0
- package/dist/assist/writing.d.ts +10 -9
- package/dist/assist/writing.d.ts.map +1 -1
- package/dist/assist/writing.js +126 -58
- package/dist/assist/writing.js.map +1 -1
- package/dist/audit/with-audit.d.ts +13 -0
- package/dist/audit/with-audit.d.ts.map +1 -1
- package/dist/audit/with-audit.js +6 -0
- package/dist/audit/with-audit.js.map +1 -1
- package/dist/autonomy/levels.d.ts +37 -0
- package/dist/autonomy/levels.d.ts.map +1 -0
- package/dist/autonomy/levels.js +51 -0
- package/dist/autonomy/levels.js.map +1 -0
- package/dist/conversations/store.d.ts +62 -0
- package/dist/conversations/store.d.ts.map +1 -0
- package/dist/conversations/store.js +0 -0
- package/dist/conversations/store.js.map +1 -0
- package/dist/identity/context.d.ts +8 -0
- package/dist/identity/context.d.ts.map +1 -1
- package/dist/identity/context.js +19 -4
- package/dist/identity/context.js.map +1 -1
- package/dist/identity/markdown.d.ts +38 -0
- package/dist/identity/markdown.d.ts.map +1 -0
- package/dist/identity/markdown.js +85 -0
- package/dist/identity/markdown.js.map +1 -0
- package/dist/index.d.ts +62 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -3
- package/dist/index.js.map +1 -1
- package/dist/progress/job-store.d.ts +41 -0
- package/dist/progress/job-store.d.ts.map +1 -0
- package/dist/progress/job-store.js +44 -0
- package/dist/progress/job-store.js.map +1 -0
- package/dist/progress/types.d.ts +21 -0
- package/dist/progress/types.d.ts.map +1 -0
- package/dist/progress/types.js +3 -0
- package/dist/progress/types.js.map +1 -0
- package/dist/prompts/render.d.ts +23 -0
- package/dist/prompts/render.d.ts.map +1 -0
- package/dist/prompts/render.js +35 -0
- package/dist/prompts/render.js.map +1 -0
- package/dist/prompts/seeds.d.ts +34 -0
- package/dist/prompts/seeds.d.ts.map +1 -0
- package/dist/prompts/seeds.js +166 -0
- package/dist/prompts/seeds.js.map +1 -0
- package/dist/prompts/store.d.ts +8 -0
- package/dist/prompts/store.d.ts.map +1 -0
- package/dist/prompts/store.js +153 -0
- package/dist/prompts/store.js.map +1 -0
- package/dist/prompts/types.d.ts +51 -0
- package/dist/prompts/types.d.ts.map +1 -0
- package/dist/prompts/types.js +24 -0
- package/dist/prompts/types.js.map +1 -0
- package/dist/providers/anthropic.d.ts +26 -3
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +60 -12
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/catalog.d.ts +41 -0
- package/dist/providers/catalog.d.ts.map +1 -0
- package/dist/providers/catalog.js +85 -0
- package/dist/providers/catalog.js.map +1 -0
- package/dist/providers/content-parts.d.ts +12 -0
- package/dist/providers/content-parts.d.ts.map +1 -0
- package/dist/providers/content-parts.js +18 -0
- package/dist/providers/content-parts.js.map +1 -0
- package/dist/providers/defaults.d.ts +44 -0
- package/dist/providers/defaults.d.ts.map +1 -0
- package/dist/providers/defaults.js +72 -0
- package/dist/providers/defaults.js.map +1 -0
- package/dist/providers/google.d.ts +20 -3
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/providers/google.js +53 -12
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/openai.d.ts +36 -4
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +113 -21
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/registry.d.ts +38 -21
- package/dist/providers/registry.d.ts.map +1 -1
- package/dist/providers/registry.js +64 -8
- package/dist/providers/registry.js.map +1 -1
- package/dist/providers/request-signal.d.ts +18 -0
- package/dist/providers/request-signal.d.ts.map +1 -0
- package/dist/providers/request-signal.js +21 -0
- package/dist/providers/request-signal.js.map +1 -0
- package/dist/providers/resolve.d.ts +13 -0
- package/dist/providers/resolve.d.ts.map +1 -0
- package/dist/providers/resolve.js +30 -0
- package/dist/providers/resolve.js.map +1 -0
- package/dist/providers/store.d.ts +70 -0
- package/dist/providers/store.d.ts.map +1 -0
- package/dist/providers/store.js +308 -0
- package/dist/providers/store.js.map +1 -0
- package/dist/providers/tool-names.d.ts +9 -0
- package/dist/providers/tool-names.d.ts.map +1 -0
- package/dist/providers/tool-names.js +56 -0
- package/dist/providers/tool-names.js.map +1 -0
- package/dist/providers/types.d.ts +87 -3
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/rag/reference-documents/ingest.d.ts +22 -0
- package/dist/rag/reference-documents/ingest.d.ts.map +1 -0
- package/dist/rag/reference-documents/ingest.js +69 -0
- package/dist/rag/reference-documents/ingest.js.map +1 -0
- package/dist/rag/reference-documents/store.d.ts +23 -0
- package/dist/rag/reference-documents/store.d.ts.map +1 -0
- package/dist/rag/reference-documents/store.js +140 -0
- package/dist/rag/reference-documents/store.js.map +1 -0
- package/dist/rag/reference-documents/types.d.ts +52 -0
- package/dist/rag/reference-documents/types.d.ts.map +1 -0
- package/dist/rag/reference-documents/types.js +32 -0
- package/dist/rag/reference-documents/types.js.map +1 -0
- package/dist/runtime/loop.d.ts +29 -7
- package/dist/runtime/loop.d.ts.map +1 -1
- package/dist/runtime/loop.js +209 -44
- package/dist/runtime/loop.js.map +1 -1
- package/dist/runtime/retry.d.ts +2 -0
- package/dist/runtime/retry.d.ts.map +1 -1
- package/dist/runtime/retry.js +1 -0
- package/dist/runtime/retry.js.map +1 -1
- package/dist/runtime/types.d.ts +14 -3
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/site-plan/analyse-brief.d.ts +8 -0
- package/dist/site-plan/analyse-brief.d.ts.map +1 -1
- package/dist/site-plan/analyse-brief.js +17 -8
- package/dist/site-plan/analyse-brief.js.map +1 -1
- package/dist/site-plan/approval.d.ts +2 -2
- package/dist/site-plan/approval.d.ts.map +1 -1
- package/dist/site-plan/approval.js +34 -4
- package/dist/site-plan/approval.js.map +1 -1
- package/dist/site-plan/content-model.d.ts +14 -0
- package/dist/site-plan/content-model.d.ts.map +1 -1
- package/dist/site-plan/content-model.js +51 -9
- package/dist/site-plan/content-model.js.map +1 -1
- package/dist/site-plan/demo-content.d.ts.map +1 -1
- package/dist/site-plan/demo-content.js +1 -4
- package/dist/site-plan/demo-content.js.map +1 -1
- package/dist/site-plan/propose-plan.d.ts +10 -0
- package/dist/site-plan/propose-plan.d.ts.map +1 -1
- package/dist/site-plan/propose-plan.js +25 -1
- package/dist/site-plan/propose-plan.js.map +1 -1
- package/dist/site-plan/site-context.d.ts +81 -0
- package/dist/site-plan/site-context.d.ts.map +1 -0
- package/dist/site-plan/site-context.js +76 -0
- package/dist/site-plan/site-context.js.map +1 -0
- package/dist/site-plan/skin-candidates.d.ts +13 -1
- package/dist/site-plan/skin-candidates.d.ts.map +1 -1
- package/dist/site-plan/skin-candidates.js +15 -0
- package/dist/site-plan/skin-candidates.js.map +1 -1
- package/dist/site-plan/structural-gaps.d.ts +32 -0
- package/dist/site-plan/structural-gaps.d.ts.map +1 -0
- package/dist/site-plan/structural-gaps.js +77 -0
- package/dist/site-plan/structural-gaps.js.map +1 -0
- package/dist/site-plan/types.d.ts +7 -0
- package/dist/site-plan/types.d.ts.map +1 -1
- package/dist/skills/frontmatter.d.ts +19 -1
- package/dist/skills/frontmatter.d.ts.map +1 -1
- package/dist/skills/frontmatter.js +29 -5
- package/dist/skills/frontmatter.js.map +1 -1
- package/dist/skills/library.d.ts +133 -0
- package/dist/skills/library.d.ts.map +1 -0
- package/dist/skills/library.js +332 -0
- package/dist/skills/library.js.map +1 -0
- package/dist/skills/types.d.ts +8 -1
- package/dist/skills/types.d.ts.map +1 -1
- package/dist/skin/generate.d.ts +22 -1
- package/dist/skin/generate.d.ts.map +1 -1
- package/dist/skin/generate.js +60 -5
- package/dist/skin/generate.js.map +1 -1
- package/dist/theme-creator/propose-theme.d.ts +83 -0
- package/dist/theme-creator/propose-theme.d.ts.map +1 -0
- package/dist/theme-creator/propose-theme.js +234 -0
- package/dist/theme-creator/propose-theme.js.map +1 -0
- package/dist/tools/core/agent-delegate.d.ts +8 -5
- package/dist/tools/core/agent-delegate.d.ts.map +1 -1
- package/dist/tools/core/agent-delegate.js +16 -6
- package/dist/tools/core/agent-delegate.js.map +1 -1
- package/dist/tools/core/content-browse.d.ts +74 -0
- package/dist/tools/core/content-browse.d.ts.map +1 -0
- package/dist/tools/core/content-browse.js +63 -0
- package/dist/tools/core/content-browse.js.map +1 -0
- package/dist/tools/core/content-schema.d.ts +86 -0
- package/dist/tools/core/content-schema.d.ts.map +1 -0
- package/dist/tools/core/content-schema.js +83 -0
- package/dist/tools/core/content-schema.js.map +1 -0
- package/dist/tools/core/deps-scan.d.ts +25 -0
- package/dist/tools/core/deps-scan.d.ts.map +1 -0
- package/dist/tools/core/deps-scan.js +85 -0
- package/dist/tools/core/deps-scan.js.map +1 -0
- package/dist/tools/core/media.d.ts +18 -2
- package/dist/tools/core/media.d.ts.map +1 -1
- package/dist/tools/core/media.js +8 -2
- package/dist/tools/core/media.js.map +1 -1
- package/dist/tools/core/not-found-log.d.ts +44 -0
- package/dist/tools/core/not-found-log.d.ts.map +1 -0
- package/dist/tools/core/not-found-log.js +32 -0
- package/dist/tools/core/not-found-log.js.map +1 -0
- package/dist/tools/core/redirects.d.ts +51 -0
- package/dist/tools/core/redirects.d.ts.map +1 -0
- package/dist/tools/core/redirects.js +50 -0
- package/dist/tools/core/redirects.js.map +1 -0
- package/package.json +6 -4
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'node:crypto';
|
|
2
|
+
import { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { CogentaError } from '@cogenta/core';
|
|
5
|
+
import { findProviderCatalogEntry } from './catalog.js';
|
|
6
|
+
/**
|
|
7
|
+
* L22 task 1bis's "Providers" screen: which LLM providers this site has
|
|
8
|
+
* configured, each one's API key, and its default model — persisted so an
|
|
9
|
+
* operator can turn a provider on from the admin instead of only via
|
|
10
|
+
* `COGENTA_LLM_API_KEY`/`cogenta.config.mjs`'s single `llm` section
|
|
11
|
+
* (`@cogenta/core`'s `llmSchema`, unchanged and still the install-time
|
|
12
|
+
* default — `agents/orchestrator.ts` imports it as the first configured
|
|
13
|
+
* provider the first time this store is read, so an existing site loses
|
|
14
|
+
* nothing by upgrading).
|
|
15
|
+
*
|
|
16
|
+
* Fiche 56 widened `provider` from the closed 3-literal union to a free
|
|
17
|
+
* string (`registry.ts`'s `ProviderName`) so a catalog id (OpenRouter,
|
|
18
|
+
* DeepSeek, Qwen, GLM, …) or an operator-chosen custom id both fit. `upsert`
|
|
19
|
+
* is the write boundary that keeps this safe: `assertValidProviderId` rejects
|
|
20
|
+
* anything that is not a plain slug (this store builds a filename directly
|
|
21
|
+
* from `provider` — see `fileFor` — so a malformed id is a path-traversal
|
|
22
|
+
* risk, not merely an aesthetic one), and a name absent from the catalog
|
|
23
|
+
* must carry its own `baseUrl` or it could never resolve to a working client
|
|
24
|
+
* (`registry.ts`'s `buildClient` throws `PROVIDER_CUSTOM_BASE_URL_REQUIRED`
|
|
25
|
+
* for exactly that case — this store refuses the same shape earlier, at the
|
|
26
|
+
* point an operator can still fix it, rather than at first agent run).
|
|
27
|
+
*
|
|
28
|
+
* "Jamais affichée en clair une fois enregistrée" (the lot's own words, same
|
|
29
|
+
* discipline as `create-cogenta`'s masked key prompt) rules out storing the
|
|
30
|
+
* key as recoverable plaintext an API response could echo back — but an LLM
|
|
31
|
+
* call still needs the real key at request time, which rules out a one-way
|
|
32
|
+
* hash (`@cogenta/auth`'s `ApiKeyStore` model: fine for a bearer token this
|
|
33
|
+
* site itself verifies, useless for a key a *vendor* must see). This store
|
|
34
|
+
* therefore encrypts at rest (AES-256-GCM, `node:crypto`, zero new
|
|
35
|
+
* dependency — R9/R10) with a key derived from `COGENTA_AUTH_SIGNING_KEY`
|
|
36
|
+
* (the secret every real deployment already has, R7 — no second secret to
|
|
37
|
+
* generate, rotate or lose) via `scryptSync` with a purpose-specific salt,
|
|
38
|
+
* so the derived key is never the literal signing key reused across
|
|
39
|
+
* purposes. `list()`/`get()` return only a masked preview; the plaintext key
|
|
40
|
+
* is decrypted solely inside `resolveProviderRegistryConfig`, which the
|
|
41
|
+
* runtime hands straight to `createProviderRegistry` and never logs or
|
|
42
|
+
* returns over the wire (R7).
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* A safe filename component and a reasonable admin-typed identifier: lower-
|
|
46
|
+
* case slug, 2-64 characters, no leading/trailing/doubled hyphen. Every
|
|
47
|
+
* catalog id in `catalog.ts` matches this by construction; the check exists
|
|
48
|
+
* for an operator-typed custom provider id.
|
|
49
|
+
*/
|
|
50
|
+
const PROVIDER_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+){0,10}$/u;
|
|
51
|
+
function assertValidProviderId(provider) {
|
|
52
|
+
if (PROVIDER_ID_PATTERN.test(provider) && provider.length <= 64)
|
|
53
|
+
return;
|
|
54
|
+
throw new CogentaError({
|
|
55
|
+
code: 'PROVIDER_ID_INVALID',
|
|
56
|
+
message: `"${provider}" is not a valid provider id.`,
|
|
57
|
+
hint: 'Use lowercase letters, digits and single hyphens only (e.g. "openrouter", "my-vllm-server").',
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/** A provider id the catalog does not know needs its own `baseUrl` — otherwise it could never resolve to a working client (see `registry.ts`'s `buildClient`). */
|
|
61
|
+
function assertResolvable(provider, baseUrl) {
|
|
62
|
+
if (findProviderCatalogEntry(provider) !== undefined)
|
|
63
|
+
return;
|
|
64
|
+
if (baseUrl !== undefined && baseUrl.trim().length > 0)
|
|
65
|
+
return;
|
|
66
|
+
throw new CogentaError({
|
|
67
|
+
code: 'PROVIDER_CUSTOM_BASE_URL_REQUIRED',
|
|
68
|
+
message: `"${provider}" is not a built-in provider — a custom provider needs a non-empty "baseUrl".`,
|
|
69
|
+
hint: 'Add a baseUrl pointing at an OpenAI-compatible chat completions endpoint, or use a catalog provider id.',
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/** Bounds wide enough for any real model/deployment, narrow enough to catch a typo (a negative, a zero, or a value nobody would deliberately set) before it reaches an HTTP request. */
|
|
73
|
+
const TUNING_BOUNDS = {
|
|
74
|
+
maxOutputTokens: { min: 1, max: 200_000 },
|
|
75
|
+
requestTimeoutMs: { min: 1000, max: 600_000 },
|
|
76
|
+
maxCorrectionAttempts: { min: 1, max: 10 },
|
|
77
|
+
};
|
|
78
|
+
function assertValidTuning(input) {
|
|
79
|
+
for (const [field, bounds] of Object.entries(TUNING_BOUNDS)) {
|
|
80
|
+
const value = input[field];
|
|
81
|
+
if (value === undefined)
|
|
82
|
+
continue;
|
|
83
|
+
if (!Number.isInteger(value) || value < bounds.min || value > bounds.max) {
|
|
84
|
+
throw new CogentaError({
|
|
85
|
+
code: 'PROVIDER_TUNING_INVALID',
|
|
86
|
+
message: `"${field}" must be a whole number between ${bounds.min} and ${bounds.max}.`,
|
|
87
|
+
hint: 'Leave it empty to use the built-in default for this field.',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const KEY_DERIVATION_SALT = 'cogenta-provider-secrets-v1';
|
|
93
|
+
const ALGORITHM = 'aes-256-gcm';
|
|
94
|
+
function deriveKey(signingKey) {
|
|
95
|
+
return scryptSync(signingKey, KEY_DERIVATION_SALT, 32);
|
|
96
|
+
}
|
|
97
|
+
function mask(apiKey) {
|
|
98
|
+
const tail = apiKey.slice(-4);
|
|
99
|
+
return tail.length === 0 ? '••••' : `••••${tail}`;
|
|
100
|
+
}
|
|
101
|
+
function providerNotConfigured(provider) {
|
|
102
|
+
return new CogentaError({
|
|
103
|
+
code: 'PROVIDER_NOT_CONFIGURED',
|
|
104
|
+
message: `No API key is saved for "${provider}".`,
|
|
105
|
+
hint: 'Configure it from the admin\'s "Providers" screen first.',
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function toSummary(record) {
|
|
109
|
+
return {
|
|
110
|
+
provider: record.provider,
|
|
111
|
+
enabled: record.enabled,
|
|
112
|
+
model: record.model,
|
|
113
|
+
...(record.baseUrl === undefined ? {} : { baseUrl: record.baseUrl }),
|
|
114
|
+
maskedKey: record.maskedKey,
|
|
115
|
+
updatedAt: record.updatedAt,
|
|
116
|
+
...(record.maxOutputTokens === undefined ? {} : { maxOutputTokens: record.maxOutputTokens }),
|
|
117
|
+
...(record.requestTimeoutMs === undefined ? {} : { requestTimeoutMs: record.requestTimeoutMs }),
|
|
118
|
+
...(record.maxCorrectionAttempts === undefined
|
|
119
|
+
? {}
|
|
120
|
+
: { maxCorrectionAttempts: record.maxCorrectionAttempts }),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/** One `<provider>.json` record per configured provider, under `options.dir` — the same one-file-per-record shape every other file store in this package uses (R1: no external service). */
|
|
124
|
+
export function createFileProviderConfigStore(options) {
|
|
125
|
+
const now = options.now ?? (() => new Date());
|
|
126
|
+
const key = deriveKey(options.signingKey);
|
|
127
|
+
const ready = mkdir(options.dir, { recursive: true });
|
|
128
|
+
/**
|
|
129
|
+
* The one place every method that touches disk builds a path from
|
|
130
|
+
* `provider` — validating here, rather than only in `upsert`, is what
|
|
131
|
+
* closes the path-traversal gap `PATCH`/`DELETE /api/providers/:provider`
|
|
132
|
+
* would otherwise reopen: the router no longer allowlists `provider`
|
|
133
|
+
* against a fixed name list before calling `setEnabled`/`updateSettings`/
|
|
134
|
+
* `remove` (fiche 56 removed that gate to admit catalog and custom ids),
|
|
135
|
+
* so this store is the only remaining checkpoint before an attacker-
|
|
136
|
+
* controlled string like `../../agents/some-agent` reaches `readFile`/
|
|
137
|
+
* `writeFile`/`rm`. A caller of `get`/`decryptKey` on a malformed id gets
|
|
138
|
+
* the same `PROVIDER_ID_INVALID` an `upsert` would — never a silent
|
|
139
|
+
* "not found" that would let the check be bypassed by relying on ENOENT.
|
|
140
|
+
*/
|
|
141
|
+
function fileFor(provider) {
|
|
142
|
+
assertValidProviderId(provider);
|
|
143
|
+
return join(options.dir, `${provider}.json`);
|
|
144
|
+
}
|
|
145
|
+
function encrypt(plaintext) {
|
|
146
|
+
const iv = randomBytes(12);
|
|
147
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
148
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
|
|
149
|
+
return {
|
|
150
|
+
iv: iv.toString('base64'),
|
|
151
|
+
authTag: cipher.getAuthTag().toString('base64'),
|
|
152
|
+
ciphertext: ciphertext.toString('base64'),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function decrypt(record) {
|
|
156
|
+
const decipher = createDecipheriv(ALGORITHM, key, Buffer.from(record.iv, 'base64'));
|
|
157
|
+
decipher.setAuthTag(Buffer.from(record.authTag, 'base64'));
|
|
158
|
+
const plaintext = Buffer.concat([
|
|
159
|
+
decipher.update(Buffer.from(record.ciphertext, 'base64')),
|
|
160
|
+
decipher.final(),
|
|
161
|
+
]);
|
|
162
|
+
return plaintext.toString('utf8');
|
|
163
|
+
}
|
|
164
|
+
async function readRecord(provider) {
|
|
165
|
+
// `fileFor` (which validates `provider`) is called outside the try
|
|
166
|
+
// block on purpose: a `PROVIDER_ID_INVALID` it throws must propagate as
|
|
167
|
+
// itself, never be caught below and rewritten into a misleading
|
|
168
|
+
// "the file may be corrupted" `INTERNAL` error.
|
|
169
|
+
const path = fileFor(provider);
|
|
170
|
+
try {
|
|
171
|
+
const raw = await readFile(path, 'utf8');
|
|
172
|
+
return JSON.parse(raw);
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
if (error.code === 'ENOENT')
|
|
176
|
+
return null;
|
|
177
|
+
throw new CogentaError({
|
|
178
|
+
code: 'INTERNAL',
|
|
179
|
+
message: `Could not read the saved configuration for provider "${provider}".`,
|
|
180
|
+
hint: 'The file may be corrupted; consider removing it and reconfiguring the provider.',
|
|
181
|
+
cause: error,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
async list() {
|
|
187
|
+
await ready;
|
|
188
|
+
const filenames = await readdir(options.dir).catch(() => []);
|
|
189
|
+
const records = [];
|
|
190
|
+
for (const filename of filenames) {
|
|
191
|
+
if (!filename.endsWith('.json'))
|
|
192
|
+
continue;
|
|
193
|
+
const provider = filename.replace(/\.json$/u, '');
|
|
194
|
+
if (!PROVIDER_ID_PATTERN.test(provider))
|
|
195
|
+
continue;
|
|
196
|
+
const record = await readRecord(provider);
|
|
197
|
+
if (record !== null)
|
|
198
|
+
records.push(toSummary(record));
|
|
199
|
+
}
|
|
200
|
+
return records.sort((a, b) => a.provider.localeCompare(b.provider));
|
|
201
|
+
},
|
|
202
|
+
async get(provider) {
|
|
203
|
+
await ready;
|
|
204
|
+
const record = await readRecord(provider);
|
|
205
|
+
return record === null ? undefined : toSummary(record);
|
|
206
|
+
},
|
|
207
|
+
async upsert(input) {
|
|
208
|
+
await ready;
|
|
209
|
+
assertValidProviderId(input.provider);
|
|
210
|
+
assertResolvable(input.provider, input.baseUrl);
|
|
211
|
+
assertValidTuning(input);
|
|
212
|
+
const { iv, authTag, ciphertext } = encrypt(input.apiKey);
|
|
213
|
+
const record = {
|
|
214
|
+
provider: input.provider,
|
|
215
|
+
enabled: input.enabled ?? true,
|
|
216
|
+
model: input.model,
|
|
217
|
+
...(input.baseUrl === undefined ? {} : { baseUrl: input.baseUrl }),
|
|
218
|
+
maskedKey: mask(input.apiKey),
|
|
219
|
+
iv,
|
|
220
|
+
authTag,
|
|
221
|
+
ciphertext,
|
|
222
|
+
updatedAt: now().toISOString(),
|
|
223
|
+
...(input.maxOutputTokens === undefined ? {} : { maxOutputTokens: input.maxOutputTokens }),
|
|
224
|
+
...(input.requestTimeoutMs === undefined
|
|
225
|
+
? {}
|
|
226
|
+
: { requestTimeoutMs: input.requestTimeoutMs }),
|
|
227
|
+
...(input.maxCorrectionAttempts === undefined
|
|
228
|
+
? {}
|
|
229
|
+
: { maxCorrectionAttempts: input.maxCorrectionAttempts }),
|
|
230
|
+
};
|
|
231
|
+
await writeFile(fileFor(input.provider), JSON.stringify(record, null, 2), 'utf8');
|
|
232
|
+
return toSummary(record);
|
|
233
|
+
},
|
|
234
|
+
async setEnabled(provider, enabled) {
|
|
235
|
+
await ready;
|
|
236
|
+
const existing = await readRecord(provider);
|
|
237
|
+
if (existing === null)
|
|
238
|
+
throw providerNotConfigured(provider);
|
|
239
|
+
const updated = { ...existing, enabled, updatedAt: now().toISOString() };
|
|
240
|
+
await writeFile(fileFor(provider), JSON.stringify(updated, null, 2), 'utf8');
|
|
241
|
+
return toSummary(updated);
|
|
242
|
+
},
|
|
243
|
+
async remove(provider) {
|
|
244
|
+
await ready;
|
|
245
|
+
await rm(fileFor(provider), { force: true });
|
|
246
|
+
},
|
|
247
|
+
async updateSettings(provider, patch) {
|
|
248
|
+
await ready;
|
|
249
|
+
const existing = await readRecord(provider);
|
|
250
|
+
if (existing === null)
|
|
251
|
+
throw providerNotConfigured(provider);
|
|
252
|
+
const nextBaseUrl = patch.baseUrl !== undefined
|
|
253
|
+
? patch.baseUrl
|
|
254
|
+
: existing.baseUrl === undefined
|
|
255
|
+
? undefined
|
|
256
|
+
: existing.baseUrl;
|
|
257
|
+
assertResolvable(provider, nextBaseUrl);
|
|
258
|
+
assertValidTuning({
|
|
259
|
+
...(patch.maxOutputTokens === undefined || patch.maxOutputTokens === null
|
|
260
|
+
? {}
|
|
261
|
+
: { maxOutputTokens: patch.maxOutputTokens }),
|
|
262
|
+
...(patch.requestTimeoutMs === undefined || patch.requestTimeoutMs === null
|
|
263
|
+
? {}
|
|
264
|
+
: { requestTimeoutMs: patch.requestTimeoutMs }),
|
|
265
|
+
...(patch.maxCorrectionAttempts === undefined || patch.maxCorrectionAttempts === null
|
|
266
|
+
? {}
|
|
267
|
+
: { maxCorrectionAttempts: patch.maxCorrectionAttempts }),
|
|
268
|
+
});
|
|
269
|
+
// `null` clears a tuning field back to "use the built-in default";
|
|
270
|
+
// `undefined` leaves it exactly as saved — the reason this can't just
|
|
271
|
+
// be `patch.x ?? existing.x` the way `model` above is. Starting from
|
|
272
|
+
// `existing` stripped of all three (rather than the full record) and
|
|
273
|
+
// re-adding only what survives is what lets a `null` actually delete
|
|
274
|
+
// the key instead of merely being overwritten back to its old value.
|
|
275
|
+
const { maxOutputTokens: _existingMaxOutputTokens, requestTimeoutMs: _existingRequestTimeoutMs, maxCorrectionAttempts: _existingMaxCorrectionAttempts, ...existingWithoutTuning } = existing;
|
|
276
|
+
const nextMaxOutputTokens = patch.maxOutputTokens === undefined
|
|
277
|
+
? existing.maxOutputTokens
|
|
278
|
+
: (patch.maxOutputTokens ?? undefined);
|
|
279
|
+
const nextRequestTimeoutMs = patch.requestTimeoutMs === undefined
|
|
280
|
+
? existing.requestTimeoutMs
|
|
281
|
+
: (patch.requestTimeoutMs ?? undefined);
|
|
282
|
+
const nextMaxCorrectionAttempts = patch.maxCorrectionAttempts === undefined
|
|
283
|
+
? existing.maxCorrectionAttempts
|
|
284
|
+
: (patch.maxCorrectionAttempts ?? undefined);
|
|
285
|
+
const updated = {
|
|
286
|
+
...existingWithoutTuning,
|
|
287
|
+
model: patch.model ?? existing.model,
|
|
288
|
+
...(nextBaseUrl === undefined ? {} : { baseUrl: nextBaseUrl }),
|
|
289
|
+
updatedAt: now().toISOString(),
|
|
290
|
+
...(nextMaxOutputTokens === undefined ? {} : { maxOutputTokens: nextMaxOutputTokens }),
|
|
291
|
+
...(nextRequestTimeoutMs === undefined ? {} : { requestTimeoutMs: nextRequestTimeoutMs }),
|
|
292
|
+
...(nextMaxCorrectionAttempts === undefined
|
|
293
|
+
? {}
|
|
294
|
+
: { maxCorrectionAttempts: nextMaxCorrectionAttempts }),
|
|
295
|
+
};
|
|
296
|
+
await writeFile(fileFor(provider), JSON.stringify(updated, null, 2), 'utf8');
|
|
297
|
+
return toSummary(updated);
|
|
298
|
+
},
|
|
299
|
+
async decryptKey(provider) {
|
|
300
|
+
await ready;
|
|
301
|
+
const record = await readRecord(provider);
|
|
302
|
+
if (record === null)
|
|
303
|
+
throw providerNotConfigured(provider);
|
|
304
|
+
return decrypt(record);
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/providers/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ChatRequest } from './types.js';
|
|
2
|
+
export declare function encodeToolName(name: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* The decoder for one request. Throws when two distinct tool names would
|
|
5
|
+
* collide on the wire (`a.b` next to `a__b`) — a misconfiguration worth a
|
|
6
|
+
* loud error, never a silently wrong tool.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createToolNameDecoder(request: ChatRequest): (wire: string) => string;
|
|
9
|
+
//# sourceMappingURL=tool-names.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-names.d.ts","sourceRoot":"","sources":["../../src/providers/tool-names.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AA4B7C,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAmBpF"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { CogentaError } from '@cogenta/core';
|
|
2
|
+
/**
|
|
3
|
+
* Contract C names its tools with a dot (`content.read`, `redirects.create`,
|
|
4
|
+
* `code.propose_patch`); every vendor wire format refuses that character in
|
|
5
|
+
* a function name — OpenAI-compatible endpoints (OpenAI, DeepSeek, Qwen,
|
|
6
|
+
* OpenRouter…) and Anthropic both enforce `^[a-zA-Z0-9_-]+$`, and Gemini
|
|
7
|
+
* has its own narrower rules. Found live: DeepSeek answered every agent run
|
|
8
|
+
* with `400 Invalid 'tools[0].function.name': string does not match
|
|
9
|
+
* pattern` before the request ever reached a model, and nothing in the
|
|
10
|
+
* catalog could have worked with a single tool declared.
|
|
11
|
+
*
|
|
12
|
+
* The fix lives here rather than in the tool names themselves: contract C
|
|
13
|
+
* is frozen, and a dot is the right separator for humans, the audit log
|
|
14
|
+
* and the permission taxonomy. Each adapter encodes on the way out and
|
|
15
|
+
* decodes on the way back, so no other layer ever sees a wire name.
|
|
16
|
+
*
|
|
17
|
+
* Encoding is deterministic (`.` and anything else outside the safe set →
|
|
18
|
+
* `__`), which is what lets an assistant message from earlier in the
|
|
19
|
+
* conversation — one that echoes a previous tool call by name — be encoded
|
|
20
|
+
* without any state. Decoding needs the reverse map, built from the request
|
|
21
|
+
* itself; a wire name the map does not know (a model inventing a tool) is
|
|
22
|
+
* returned unchanged, and the runtime's own registry refuses it downstream
|
|
23
|
+
* exactly as it would have before.
|
|
24
|
+
*/
|
|
25
|
+
const UNSAFE = /[^a-zA-Z0-9_-]/gu;
|
|
26
|
+
export function encodeToolName(name) {
|
|
27
|
+
return name.replace(UNSAFE, '__');
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The decoder for one request. Throws when two distinct tool names would
|
|
31
|
+
* collide on the wire (`a.b` next to `a__b`) — a misconfiguration worth a
|
|
32
|
+
* loud error, never a silently wrong tool.
|
|
33
|
+
*/
|
|
34
|
+
export function createToolNameDecoder(request) {
|
|
35
|
+
const byWire = new Map();
|
|
36
|
+
const register = (name) => {
|
|
37
|
+
const wire = encodeToolName(name);
|
|
38
|
+
const existing = byWire.get(wire);
|
|
39
|
+
if (existing !== undefined && existing !== name) {
|
|
40
|
+
throw new CogentaError({
|
|
41
|
+
code: 'PROVIDER_REQUEST_FAILED',
|
|
42
|
+
message: `Tool names "${existing}" and "${name}" are indistinguishable once encoded for the provider ("${wire}").`,
|
|
43
|
+
hint: 'Rename one of the two tools so they differ by more than characters the wire format refuses.',
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
byWire.set(wire, name);
|
|
47
|
+
};
|
|
48
|
+
for (const tool of request.tools ?? [])
|
|
49
|
+
register(tool.name);
|
|
50
|
+
for (const message of request.messages) {
|
|
51
|
+
for (const call of message.toolCalls ?? [])
|
|
52
|
+
register(call.name);
|
|
53
|
+
}
|
|
54
|
+
return (wire) => byWire.get(wire) ?? wire;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=tool-names.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-names.js","sourceRoot":"","sources":["../../src/providers/tool-names.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAG5C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,MAAM,GAAG,kBAAkB,CAAA;AAEjC,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;AACnC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAoB;IACxD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAA;IACxC,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAQ,EAAE;QACtC,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,CAAA;QACjC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QACjC,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YAChD,MAAM,IAAI,YAAY,CAAC;gBACrB,IAAI,EAAE,yBAAyB;gBAC/B,OAAO,EAAE,eAAe,QAAQ,UAAU,IAAI,2DAA2D,IAAI,KAAK;gBAClH,IAAI,EAAE,6FAA6F;aACpG,CAAC,CAAA;QACJ,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IACxB,CAAC,CAAA;IACD,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,IAAI,EAAE;QAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC3D,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACvC,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,SAAS,IAAI,EAAE;YAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACjE,CAAC;IACD,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAA;AAC3C,CAAC"}
|
|
@@ -13,10 +13,33 @@ export interface ProviderToolCall {
|
|
|
13
13
|
readonly name: string;
|
|
14
14
|
readonly input: Readonly<Record<string, unknown>>;
|
|
15
15
|
}
|
|
16
|
+
/** A plain-text segment of a multimodal message. */
|
|
17
|
+
export interface ChatTextPart {
|
|
18
|
+
readonly type: 'text';
|
|
19
|
+
readonly text: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* An inline image segment of a multimodal message. `data` is raw base64 —
|
|
23
|
+
* no `data:` URI prefix — so each adapter can format it however its own
|
|
24
|
+
* wire protocol wants it (a data URL for OpenAI, a bare base64 field for
|
|
25
|
+
* Anthropic and Google).
|
|
26
|
+
*/
|
|
27
|
+
export interface ChatImagePart {
|
|
28
|
+
readonly type: 'image';
|
|
29
|
+
readonly mediaType: string;
|
|
30
|
+
readonly data: string;
|
|
31
|
+
}
|
|
32
|
+
export type ChatContentPart = ChatTextPart | ChatImagePart;
|
|
16
33
|
export interface ChatMessage {
|
|
17
34
|
readonly role: ChatRole;
|
|
18
|
-
/**
|
|
19
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Absent (not empty-string) when a turn is tool calls with no accompanying
|
|
37
|
+
* text. A plain `string` is the common case and every existing caller
|
|
38
|
+
* keeps compiling and behaving identically; an array of `ChatContentPart`
|
|
39
|
+
* lets a caller attach images alongside text (e.g. the Theme Creator
|
|
40
|
+
* agent handing a model a screenshot a user uploaded).
|
|
41
|
+
*/
|
|
42
|
+
readonly content?: string | readonly ChatContentPart[];
|
|
20
43
|
/** Set on an `assistant` message that requested tool calls. */
|
|
21
44
|
readonly toolCalls?: readonly ProviderToolCall[];
|
|
22
45
|
/** Set on a `tool` message: which call this is the result of. */
|
|
@@ -35,7 +58,14 @@ export interface ChatRequest {
|
|
|
35
58
|
readonly system?: string;
|
|
36
59
|
readonly messages: readonly ChatMessage[];
|
|
37
60
|
readonly tools?: readonly ProviderToolSpec[];
|
|
38
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Absent lets the client fall back to its own configured
|
|
63
|
+
* `maxOutputTokens` (an admin-set property of the provider/model, not a
|
|
64
|
+
* literal a caller should be picking) — see `ProviderClient.maxOutputTokens`.
|
|
65
|
+
* A caller only sets this for a genuine, call-specific reason to differ
|
|
66
|
+
* from that default.
|
|
67
|
+
*/
|
|
68
|
+
readonly maxTokens?: number;
|
|
39
69
|
readonly temperature?: number;
|
|
40
70
|
}
|
|
41
71
|
export type StopReason = 'end_turn' | 'tool_use' | 'max_tokens' | 'stop_sequence';
|
|
@@ -58,6 +88,60 @@ export interface ChatOptions {
|
|
|
58
88
|
export interface ProviderClient {
|
|
59
89
|
readonly name: string;
|
|
60
90
|
readonly model: string;
|
|
91
|
+
/**
|
|
92
|
+
* Whether this client can accept a `ChatImagePart` in a message's content.
|
|
93
|
+
* Absent means "unknown, assume no" — a caller building a multimodal
|
|
94
|
+
* message should check this before attaching an image, and degrade
|
|
95
|
+
* (text-only, with an explicit note) when it is not `true`.
|
|
96
|
+
*/
|
|
97
|
+
readonly supportsVision?: boolean;
|
|
98
|
+
/**
|
|
99
|
+
* The completion budget every call through this client defaults to when
|
|
100
|
+
* a request does not set its own `maxTokens` — a property of *which
|
|
101
|
+
* model this is*, not of any one call site. A reasoning-tier model can
|
|
102
|
+
* spend thousands of tokens "thinking" before it ever writes a visible
|
|
103
|
+
* answer, and that reasoning counts against this same budget; a plain
|
|
104
|
+
* instruct model needs far less. This is why it lives on the provider
|
|
105
|
+
* (admin-configurable from `/admin/providers`, `resolve.ts` resolves it
|
|
106
|
+
* from `ProviderConfigStore`), not as a constant scattered across every
|
|
107
|
+
* caller — a real, reproduced bug (`docs`/changeset history: DeepSeek's
|
|
108
|
+
* `deepseek-v4-flash` hitting `finish_reason: "length"` with empty
|
|
109
|
+
* content at a too-low hardcoded ceiling) is exactly what a per-call
|
|
110
|
+
* hardcoded number cannot adapt to.
|
|
111
|
+
*
|
|
112
|
+
* **Always a concrete number, never `undefined`** — fiche feedback: this
|
|
113
|
+
* used to be optional, with the site-wide floor a resolved adapter falls
|
|
114
|
+
* back to (when an admin never set a per-provider override) living as a
|
|
115
|
+
* TypeScript constant (`FALLBACK_MAX_OUTPUT_TOKENS`), invisible and
|
|
116
|
+
* unreachable from the admin. That floor is now itself a real site
|
|
117
|
+
* setting (`assistant.defaultMaxOutputTokens`, `SITE_SETTINGS_REGISTRY`
|
|
118
|
+
* in `@cogenta/schema`) a resolved adapter
|
|
119
|
+
* (`createAnthropicClient`/`createOpenAiClient`/`createGoogleClient`)
|
|
120
|
+
* always merges in before ever returning a client — so a caller never
|
|
121
|
+
* needs its own `?? literal`, and the type now says so.
|
|
122
|
+
*/
|
|
123
|
+
readonly maxOutputTokens: number;
|
|
124
|
+
/**
|
|
125
|
+
* How many times a generate-validate-correct loop (skin generation, brief
|
|
126
|
+
* analysis, content-model/demo-content proposals, the base-theme choice)
|
|
127
|
+
* retries this client before giving up. Same reasoning as
|
|
128
|
+
* `maxOutputTokens`, site-wide floor included:
|
|
129
|
+
* `assistant.defaultMaxCorrectionAttempts` — always a concrete number.
|
|
130
|
+
*/
|
|
131
|
+
readonly maxCorrectionAttempts: number;
|
|
132
|
+
/**
|
|
133
|
+
* How long a single HTTP call to this provider is allowed to run before
|
|
134
|
+
* being aborted — both the raw request timeout each adapter applies via
|
|
135
|
+
* `requestSignalWithTimeout`, and the per-model-call timeout the LangGraph
|
|
136
|
+
* agent loop (`runtime/loop.ts`) enforces around `chat()`. A slower
|
|
137
|
+
* (often reasoning-tier) model genuinely needs more wall-clock time, not
|
|
138
|
+
* just more tokens; a fixed timeout picked without knowing which model an
|
|
139
|
+
* admin configured is the same class of mistake as a fixed token budget.
|
|
140
|
+
* Site-wide floor: `assistant.defaultRequestTimeoutSeconds` — always a
|
|
141
|
+
* concrete number, in milliseconds here (the setting itself is seconds,
|
|
142
|
+
* matching what the admin form shows).
|
|
143
|
+
*/
|
|
144
|
+
readonly requestTimeoutMs: number;
|
|
61
145
|
chat(request: ChatRequest, options?: ChatOptions): Promise<ChatResponse>;
|
|
62
146
|
}
|
|
63
147
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAA;AAEpD,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAClD;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;IACvB
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAA;AAEpD,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAClD;AAED,oDAAoD;AACpD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,aAAa,CAAA;AAE1D,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;IACvB;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,eAAe,EAAE,CAAA;IACtD,+DAA+D;IAC/D,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;IAChD,iEAAiE;IACjE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,0FAA0F;IAC1F,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,oJAAoJ;AACpJ,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CACxD;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAA;IACzC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;IAC5C;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAC9B;AAED,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,YAAY,GAAG,eAAe,CAAA;AAEjF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC9B;AAED,MAAM,WAAW,YAAY;IAC3B,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,QAAQ,CAAC,SAAS,EAAE,SAAS,gBAAgB,EAAE,CAAA;IAC/C,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAA;IAC/B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;CAC3B;AAED,MAAM,WAAW,WAAW;IAC1B,yHAAyH;IACzH,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAC9B;AAED,wHAAwH;AACxH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAA;IACjC;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAChC;;;;;;OAMG;IACH,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAA;IACtC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;CACzE"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ExtractedDocument } from '../../documents/extract-text.js';
|
|
2
|
+
import type { EmbeddingProvider } from '../embeddings/types.js';
|
|
3
|
+
import type { VectorStore } from '../vector/types.js';
|
|
4
|
+
export interface ReferenceDocumentIngestOptions {
|
|
5
|
+
readonly store: VectorStore;
|
|
6
|
+
readonly embeddings: EmbeddingProvider;
|
|
7
|
+
readonly siteId: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Chunks, embeds and stores one uploaded reference document under the
|
|
11
|
+
* reserved pseudo-collection (`types.ts`). `documentId` is the
|
|
12
|
+
* `ReferenceDocumentStore` row's own id — reusing it as the vector
|
|
13
|
+
* `entryId` is what lets `removeReferenceDocument` and a re-upload address
|
|
14
|
+
* exactly the chunks this document produced, the same way a content entry's
|
|
15
|
+
* id already does for `withVectorIndexing`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function ingestReferenceDocument(document: Pick<ExtractedDocument, 'filename' | 'text'>, documentId: string, options: ReferenceDocumentIngestOptions): Promise<{
|
|
18
|
+
readonly chunkCount: number;
|
|
19
|
+
}>;
|
|
20
|
+
/** Removes every chunk a document produced — an upload's own undo, and what a delete from the admin screen calls. */
|
|
21
|
+
export declare function removeReferenceDocumentVectors(store: VectorStore, siteId: string, documentId: string): Promise<void>;
|
|
22
|
+
//# sourceMappingURL=ingest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../../src/rag/reference-documents/ingest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA;AAGxE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC/D,OAAO,KAAK,EAAgB,WAAW,EAAE,MAAM,oBAAoB,CAAA;AA4BnE,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAA;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,IAAI,CAAC,iBAAiB,EAAE,UAAU,GAAG,MAAM,CAAC,EACtD,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,8BAA8B,GACtC,OAAO,CAAC;IAAE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CA8B1C;AAED,qHAAqH;AACrH,wBAAsB,8BAA8B,CAClD,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAMf"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { chunkDocument } from '../chunking/chunk-document.js';
|
|
2
|
+
import { REFERENCE_DOCUMENT_COLLECTION, REFERENCE_DOCUMENT_LOCALE, REFERENCE_DOCUMENT_STATUS, } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* The one real user of `chunkDocument` (L18 task 15) against an actual
|
|
5
|
+
* multi-chunk document — `withVectorIndexing`'s own `recordFor` deliberately
|
|
6
|
+
* uses one chunk per entry instead (see that file's comment: real chunking
|
|
7
|
+
* "is a piece of work of its own"). A reference document has no blocks to
|
|
8
|
+
* read, only the flat string `extractDocumentText` already produced, so this
|
|
9
|
+
* is that piece of work, scoped to exactly what an uploaded document needs:
|
|
10
|
+
* paragraphs standing in for blocks, a blank line standing in for the
|
|
11
|
+
* boundary between them, and nothing claiming to be a heading (a plain-text
|
|
12
|
+
* or a PDF extraction has no reliable way to tell one from a paragraph, so
|
|
13
|
+
* claiming otherwise would just fabricate section boundaries `chunkDocument`
|
|
14
|
+
* would then treat as if a human had marked them).
|
|
15
|
+
*/
|
|
16
|
+
function paragraphsOf(text) {
|
|
17
|
+
return text
|
|
18
|
+
.split(/\n{2,}/u)
|
|
19
|
+
.map((paragraph) => paragraph.trim())
|
|
20
|
+
.filter((paragraph) => paragraph.length > 0)
|
|
21
|
+
.map((paragraph, index) => ({ id: String(index), text: paragraph }));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Chunks, embeds and stores one uploaded reference document under the
|
|
25
|
+
* reserved pseudo-collection (`types.ts`). `documentId` is the
|
|
26
|
+
* `ReferenceDocumentStore` row's own id — reusing it as the vector
|
|
27
|
+
* `entryId` is what lets `removeReferenceDocument` and a re-upload address
|
|
28
|
+
* exactly the chunks this document produced, the same way a content entry's
|
|
29
|
+
* id already does for `withVectorIndexing`.
|
|
30
|
+
*/
|
|
31
|
+
export async function ingestReferenceDocument(document, documentId, options) {
|
|
32
|
+
const blocks = paragraphsOf(document.text);
|
|
33
|
+
if (blocks.length === 0)
|
|
34
|
+
return { chunkCount: 0 };
|
|
35
|
+
const chunks = chunkDocument({
|
|
36
|
+
documentId,
|
|
37
|
+
title: document.filename,
|
|
38
|
+
blocks,
|
|
39
|
+
});
|
|
40
|
+
if (chunks.length === 0)
|
|
41
|
+
return { chunkCount: 0 };
|
|
42
|
+
const vectors = await options.embeddings.embed(chunks.map((chunk) => chunk.text));
|
|
43
|
+
const records = [];
|
|
44
|
+
chunks.forEach((chunk, index) => {
|
|
45
|
+
const vector = vectors[index];
|
|
46
|
+
if (vector === undefined)
|
|
47
|
+
return;
|
|
48
|
+
records.push({
|
|
49
|
+
siteId: options.siteId,
|
|
50
|
+
collection: REFERENCE_DOCUMENT_COLLECTION,
|
|
51
|
+
entryId: documentId,
|
|
52
|
+
locale: REFERENCE_DOCUMENT_LOCALE,
|
|
53
|
+
status: REFERENCE_DOCUMENT_STATUS,
|
|
54
|
+
chunk,
|
|
55
|
+
vector,
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
await options.store.upsert(records);
|
|
59
|
+
return { chunkCount: records.length };
|
|
60
|
+
}
|
|
61
|
+
/** Removes every chunk a document produced — an upload's own undo, and what a delete from the admin screen calls. */
|
|
62
|
+
export async function removeReferenceDocumentVectors(store, siteId, documentId) {
|
|
63
|
+
await store.removeEntries({
|
|
64
|
+
siteId,
|
|
65
|
+
collection: REFERENCE_DOCUMENT_COLLECTION,
|
|
66
|
+
entryIds: [documentId],
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=ingest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingest.js","sourceRoot":"","sources":["../../../src/rag/reference-documents/ingest.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAI7D,OAAO,EACL,6BAA6B,EAC7B,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,YAAY,CAAA;AAEnB;;;;;;;;;;;;GAYG;AACH,SAAS,YAAY,CAAC,IAAY;IAChC,OAAO,IAAI;SACR,KAAK,CAAC,SAAS,CAAC;SAChB,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;SACpC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;SAC3C,GAAG,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAA;AACxE,CAAC;AAQD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,QAAsD,EACtD,UAAkB,EAClB,OAAuC;IAEvC,MAAM,MAAM,GAAG,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC1C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,UAAU,EAAE,CAAC,EAAE,CAAA;IAEjD,MAAM,MAAM,GAAG,aAAa,CAAC;QAC3B,UAAU;QACV,KAAK,EAAE,QAAQ,CAAC,QAAQ;QACxB,MAAM;KACP,CAAC,CAAA;IACF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,UAAU,EAAE,CAAC,EAAE,CAAA;IAEjD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAA;IAEjF,MAAM,OAAO,GAAmB,EAAE,CAAA;IAClC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QAC9B,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;QAC7B,IAAI,MAAM,KAAK,SAAS;YAAE,OAAM;QAChC,OAAO,CAAC,IAAI,CAAC;YACX,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,UAAU,EAAE,6BAA6B;YACzC,OAAO,EAAE,UAAU;YACnB,MAAM,EAAE,yBAAyB;YACjC,MAAM,EAAE,yBAAyB;YACjC,KAAK;YACL,MAAM;SACP,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,MAAM,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACnC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,MAAM,EAAE,CAAA;AACvC,CAAC;AAED,qHAAqH;AACrH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAClD,KAAkB,EAClB,MAAc,EACd,UAAkB;IAElB,MAAM,KAAK,CAAC,aAAa,CAAC;QACxB,MAAM;QACN,UAAU,EAAE,6BAA6B;QACzC,QAAQ,EAAE,CAAC,UAAU,CAAC;KACvB,CAAC,CAAA;AACJ,CAAC"}
|