primitive-admin 1.1.0-alpha.83 → 1.1.0-alpha.85
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 +43 -1
- package/assets/skill/skills/primitive-platform/SKILL.md +105 -675
- package/dist/src/commands/admins.js +29 -32
- package/dist/src/commands/admins.js.map +1 -1
- package/dist/src/commands/analytics.js +2 -1
- package/dist/src/commands/analytics.js.map +1 -1
- package/dist/src/commands/apps.js +12 -5
- package/dist/src/commands/apps.js.map +1 -1
- package/dist/src/commands/blob-buckets.js +2 -1
- package/dist/src/commands/blob-buckets.js.map +1 -1
- package/dist/src/commands/catalog.js +26 -10
- package/dist/src/commands/catalog.js.map +1 -1
- package/dist/src/commands/collection-type-configs.js +2 -1
- package/dist/src/commands/collection-type-configs.js.map +1 -1
- package/dist/src/commands/collections.js +88 -18
- package/dist/src/commands/collections.js.map +1 -1
- package/dist/src/commands/connections.js +8 -9
- package/dist/src/commands/connections.js.map +1 -1
- package/dist/src/commands/cron-triggers.js +2 -1
- package/dist/src/commands/cron-triggers.js.map +1 -1
- package/dist/src/commands/database-type-configs.js +3 -2
- package/dist/src/commands/database-type-configs.js.map +1 -1
- package/dist/src/commands/databases.js +26 -10
- package/dist/src/commands/databases.js.map +1 -1
- package/dist/src/commands/documents.d.ts +38 -0
- package/dist/src/commands/documents.js +258 -64
- package/dist/src/commands/documents.js.map +1 -1
- package/dist/src/commands/email-templates.js +2 -1
- package/dist/src/commands/email-templates.js.map +1 -1
- package/dist/src/commands/env.js +11 -1
- package/dist/src/commands/env.js.map +1 -1
- package/dist/src/commands/feature-flags.js +2 -1
- package/dist/src/commands/feature-flags.js.map +1 -1
- package/dist/src/commands/functions.js +3 -2
- package/dist/src/commands/functions.js.map +1 -1
- package/dist/src/commands/group-type-configs.js +2 -1
- package/dist/src/commands/group-type-configs.js.map +1 -1
- package/dist/src/commands/groups.js +29 -8
- package/dist/src/commands/groups.js.map +1 -1
- package/dist/src/commands/guides.js +17 -11
- package/dist/src/commands/guides.js.map +1 -1
- package/dist/src/commands/integrations.js +26 -9
- package/dist/src/commands/integrations.js.map +1 -1
- package/dist/src/commands/locks.js +2 -1
- package/dist/src/commands/locks.js.map +1 -1
- package/dist/src/commands/metadata-category-configs.js +2 -1
- package/dist/src/commands/metadata-category-configs.js.map +1 -1
- package/dist/src/commands/metadata.js +8 -1
- package/dist/src/commands/metadata.js.map +1 -1
- package/dist/src/commands/prompts.js +28 -11
- package/dist/src/commands/prompts.js.map +1 -1
- package/dist/src/commands/rule-sets.js +2 -1
- package/dist/src/commands/rule-sets.js.map +1 -1
- package/dist/src/commands/scripts.js +15 -6
- package/dist/src/commands/scripts.js.map +1 -1
- package/dist/src/commands/secrets.js +2 -1
- package/dist/src/commands/secrets.js.map +1 -1
- package/dist/src/commands/sessions.js +10 -9
- package/dist/src/commands/sessions.js.map +1 -1
- package/dist/src/commands/sync.d.ts +64 -0
- package/dist/src/commands/sync.js +347 -17
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/tokens.js +2 -1
- package/dist/src/commands/tokens.js.map +1 -1
- package/dist/src/commands/users.js +16 -9
- package/dist/src/commands/users.js.map +1 -1
- package/dist/src/commands/vars.js +2 -1
- package/dist/src/commands/vars.js.map +1 -1
- package/dist/src/commands/waitlist.js +2 -1
- package/dist/src/commands/waitlist.js.map +1 -1
- package/dist/src/commands/webhooks.js +17 -10
- package/dist/src/commands/webhooks.js.map +1 -1
- package/dist/src/commands/workflows.js +36 -18
- package/dist/src/commands/workflows.js.map +1 -1
- package/dist/src/lib/api-client.d.ts +111 -1
- package/dist/src/lib/api-client.js +179 -66
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/config-object-descriptor.js +29 -7
- package/dist/src/lib/config-object-descriptor.js.map +1 -1
- package/dist/src/lib/config-payload.js +27 -2
- package/dist/src/lib/config-payload.js.map +1 -1
- package/dist/src/lib/config-surface.d.ts +2 -1
- package/dist/src/lib/config-surface.js +50 -3
- package/dist/src/lib/config-surface.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTypeIR.js +8 -0
- package/dist/src/lib/db-codegen/dbTypeIR.js.map +1 -1
- package/dist/src/lib/document-ingest-rows.d.ts +137 -0
- package/dist/src/lib/document-ingest-rows.js +181 -0
- package/dist/src/lib/document-ingest-rows.js.map +1 -0
- package/dist/src/lib/document-ingest.d.ts +25 -2
- package/dist/src/lib/document-ingest.js +184 -31
- package/dist/src/lib/document-ingest.js.map +1 -1
- package/dist/src/lib/function-db-types.d.ts +1 -1
- package/dist/src/lib/function-db-types.js +1 -1
- package/dist/src/lib/function-document-types.js +23 -2
- package/dist/src/lib/function-document-types.js.map +1 -1
- package/dist/src/lib/generated-config-surfaces.d.ts +182 -1
- package/dist/src/lib/generated-config-surfaces.js +441 -2
- package/dist/src/lib/generated-config-surfaces.js.map +1 -1
- package/dist/src/lib/generated-sdk-types.d.ts +1 -1
- package/dist/src/lib/generated-sdk-types.js +1 -1
- package/dist/src/lib/generated-sdk-types.js.map +1 -1
- package/dist/src/lib/list-options.d.ts +68 -0
- package/dist/src/lib/list-options.js +89 -0
- package/dist/src/lib/list-options.js.map +1 -0
- package/dist/src/lib/log-inspection.d.ts +16 -0
- package/dist/src/lib/log-inspection.js +23 -0
- package/dist/src/lib/log-inspection.js.map +1 -1
- package/dist/src/lib/paginate.d.ts +15 -0
- package/dist/src/lib/paginate.js +17 -0
- package/dist/src/lib/paginate.js.map +1 -1
- package/dist/src/lib/resolve-owner.d.ts +19 -0
- package/dist/src/lib/resolve-owner.js +20 -0
- package/dist/src/lib/resolve-owner.js.map +1 -0
- package/dist/src/lib/skill-installer.d.ts +49 -3
- package/dist/src/lib/skill-installer.js +275 -100
- package/dist/src/lib/skill-installer.js.map +1 -1
- package/dist/src/lib/snapshot-build.d.ts +50 -0
- package/dist/src/lib/snapshot-build.js +111 -0
- package/dist/src/lib/snapshot-build.js.map +1 -0
- package/package.json +2 -2
|
@@ -365,13 +365,98 @@ export function handlerReferences(table) {
|
|
|
365
365
|
}
|
|
366
366
|
return [...refs].sort();
|
|
367
367
|
}
|
|
368
|
-
|
|
368
|
+
// ── TOML groups (#3626) ──────────────────────────────────────────────────
|
|
369
|
+
//
|
|
370
|
+
// A field may declare the sub-table it is authored under (`tomlGroup`), and the
|
|
371
|
+
// table declares the closed set of names it admits (`tomlGroups`). These four
|
|
372
|
+
// functions are the whole mechanism: everything else — the pull projection, the
|
|
373
|
+
// push payload builder, the CLI's unknown-key and declared-type checks — reads
|
|
374
|
+
// the declaration through them.
|
|
375
|
+
/** The group names this table declares, sorted. Empty for an ungrouped table. */
|
|
376
|
+
export function tomlGroupNames(table) {
|
|
377
|
+
return Object.keys(table.tomlGroups ?? {}).sort();
|
|
378
|
+
}
|
|
379
|
+
/** The TOML keys accepted INSIDE `group`: every field that declares it. */
|
|
380
|
+
export function acceptedGroupTomlKeys(table, group) {
|
|
381
|
+
return new Set(table.fields.filter((f) => f.tomlGroup === group).map((f) => f.tomlKey));
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* TOML keys this table accepts AT ITS ROOT: the UNGROUPED fields, the declared
|
|
385
|
+
* group names, and the declared extras.
|
|
386
|
+
*
|
|
387
|
+
* A grouped leaf written at the root (`questions` directly under `[[configs]]`)
|
|
388
|
+
* is therefore an unknown key rather than one the payload builder silently
|
|
389
|
+
* ignores (SO3626-013) — the builder reads a grouped field only from
|
|
390
|
+
* `source[group][tomlKey]`, so accepting it here would drop the author's value
|
|
391
|
+
* without a word. The nine deprecated flat chat keys stay accepted through
|
|
392
|
+
* their `tomlOnlyKeys` declarations, which is what keeps old files pushing.
|
|
393
|
+
*/
|
|
369
394
|
export function acceptedTomlKeys(table) {
|
|
370
395
|
return new Set([
|
|
371
|
-
...table.fields.map((f) => f.tomlKey),
|
|
396
|
+
...table.fields.filter((f) => !f.tomlGroup).map((f) => f.tomlKey),
|
|
397
|
+
...tomlGroupNames(table),
|
|
372
398
|
...Object.keys(table.tomlOnlyKeys),
|
|
373
399
|
]);
|
|
374
400
|
}
|
|
401
|
+
/**
|
|
402
|
+
* Keys inside a group table that no field declares under it, as
|
|
403
|
+
* `"<group>.<key>"` — plus the bare `"<group>"` for a group value that is not a
|
|
404
|
+
* table at all (`chat = "yes"`), which has no keys to check and would otherwise
|
|
405
|
+
* be read as an empty block.
|
|
406
|
+
*/
|
|
407
|
+
export function findUnknownGroupedTomlKeys(tomlTable, table) {
|
|
408
|
+
if (!tomlTable || typeof tomlTable !== "object" || Array.isArray(tomlTable)) {
|
|
409
|
+
return [];
|
|
410
|
+
}
|
|
411
|
+
const entry = tomlTable;
|
|
412
|
+
const out = [];
|
|
413
|
+
for (const group of tomlGroupNames(table)) {
|
|
414
|
+
if (!(group in entry))
|
|
415
|
+
continue;
|
|
416
|
+
const value = entry[group];
|
|
417
|
+
if (value === undefined || value === null)
|
|
418
|
+
continue;
|
|
419
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
420
|
+
out.push(group);
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
const known = acceptedGroupTomlKeys(table, group);
|
|
424
|
+
for (const key of Object.keys(value)) {
|
|
425
|
+
if (!known.has(key))
|
|
426
|
+
out.push(`${group}.${key}`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return out.sort();
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Group declarations that do not hold together: a field naming a group the
|
|
433
|
+
* table does not declare, a declared group no field is authored under, and a
|
|
434
|
+
* declaration with no reason. All three are failures, for the reason
|
|
435
|
+
* `notExposed` carries a note — a name in a list with nothing behind it reads
|
|
436
|
+
* as a decision and is not one.
|
|
437
|
+
*/
|
|
438
|
+
export function findBadTomlGroupDeclarations(table) {
|
|
439
|
+
const declared = new Set(tomlGroupNames(table));
|
|
440
|
+
const used = new Set(table.fields
|
|
441
|
+
.map((f) => f.tomlGroup)
|
|
442
|
+
.filter((g) => typeof g === "string"));
|
|
443
|
+
const bad = [];
|
|
444
|
+
for (const field of table.fields) {
|
|
445
|
+
if (field.tomlGroup && !declared.has(field.tomlGroup)) {
|
|
446
|
+
bad.push(`${field.field}: tomlGroup "${field.tomlGroup}" is not declared in tomlGroups`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
for (const [group, entry] of Object.entries(table.tomlGroups ?? {})) {
|
|
450
|
+
if (!entry || typeof entry.note !== "string" || !entry.note.trim()) {
|
|
451
|
+
bad.push(`${group}: tomlGroups entries need a note`);
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
if (!used.has(group)) {
|
|
455
|
+
bad.push(`${group}: declared in tomlGroups but no field is authored under it`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return bad.sort();
|
|
459
|
+
}
|
|
375
460
|
// ── Document-level acceptance ────────────────────────────────────────────
|
|
376
461
|
//
|
|
377
462
|
// `acceptedTomlKeys` answers "is this key accepted inside this table". These
|
|
@@ -4770,6 +4855,260 @@ function geminiResolution(model, effort, budget) {
|
|
|
4770
4855
|
},
|
|
4771
4856
|
};
|
|
4772
4857
|
}
|
|
4858
|
+
// ── src/config-surface/prompt-kinds.ts ───────────────────────────────────
|
|
4859
|
+
/**
|
|
4860
|
+
* What a prompt's KIND admits — issue #3626.
|
|
4861
|
+
*
|
|
4862
|
+
* A prompt declares `[prompt] kind = "chat" | "decisions"`. The kind decides
|
|
4863
|
+
* the input envelope (`variables` rendered into templates for chat, a `state`
|
|
4864
|
+
* value for decisions), the provider endpoint the executor posts to, and which
|
|
4865
|
+
* `[configs.<kind>]` block a `[[configs]]` entry may carry. Adding a kind is a
|
|
4866
|
+
* group declaration plus its fields, not another round of implicit
|
|
4867
|
+
* discrimination: the earlier design inferred "this is a decisions config" from
|
|
4868
|
+
* the presence of `questions` on a table otherwise shaped for chat, and refused
|
|
4869
|
+
* the nine chat keys one by one.
|
|
4870
|
+
*
|
|
4871
|
+
* This module is the whole rule. `resolvePromptKind` is called by the CLI's
|
|
4872
|
+
* push preflight through the vendored artifact, by `createAppPrompt`,
|
|
4873
|
+
* `createPromptConfig` and `updatePromptConfig`, so a refusal at push and a
|
|
4874
|
+
* refusal at the admin route cannot disagree — the `resolvePromptReasoning`
|
|
4875
|
+
* posture of #3358, for the same reason.
|
|
4876
|
+
*
|
|
4877
|
+
* Pure and dependency-free, like every module under `src/config-surface/`.
|
|
4878
|
+
*/
|
|
4879
|
+
/** The kinds a prompt may declare. An omitted `kind` is `chat`. */
|
|
4880
|
+
export const PROMPT_KINDS = ["chat", "decisions"];
|
|
4881
|
+
/**
|
|
4882
|
+
* The nine settings that describe a CHAT COMPLETION and nothing else. They are
|
|
4883
|
+
* authored under `[configs.chat]`; the flat spelling is deprecated (#3642).
|
|
4884
|
+
*/
|
|
4885
|
+
export const CHAT_CONFIG_KEYS = [
|
|
4886
|
+
"systemPrompt",
|
|
4887
|
+
"userPromptTemplate",
|
|
4888
|
+
"temperature",
|
|
4889
|
+
"topP",
|
|
4890
|
+
"maxTokens",
|
|
4891
|
+
"outputFormat",
|
|
4892
|
+
"outputSchema",
|
|
4893
|
+
"reasoningEffort",
|
|
4894
|
+
"reasoningBudget",
|
|
4895
|
+
];
|
|
4896
|
+
/** The settings that describe a DECISIONS request. */
|
|
4897
|
+
export const DECISIONS_CONFIG_KEYS = ["questions"];
|
|
4898
|
+
/**
|
|
4899
|
+
* The WIRE field names each kind's block holds. The config surface declares the
|
|
4900
|
+
* same grouping through `ConfigField.tomlGroup`, and the drift guard asserts
|
|
4901
|
+
* the two agree — so a chat key added to the surface without a home here (or
|
|
4902
|
+
* the reverse) fails `cli-unit-tests` rather than being accepted under a
|
|
4903
|
+
* decisions prompt.
|
|
4904
|
+
*/
|
|
4905
|
+
export const PROMPT_KIND_BLOCKS = {
|
|
4906
|
+
chat: CHAT_CONFIG_KEYS,
|
|
4907
|
+
decisions: DECISIONS_CONFIG_KEYS,
|
|
4908
|
+
};
|
|
4909
|
+
/** The question types TypeSafe's System One models answer. */
|
|
4910
|
+
export const DECISIONS_QUESTION_TYPES = ["choice", "score", "noul"];
|
|
4911
|
+
/** A question name the endpoint accepts, and that reads as a JSON key. */
|
|
4912
|
+
const QUESTION_NAME = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
4913
|
+
/**
|
|
4914
|
+
* The prompt's kind, normalized.
|
|
4915
|
+
*
|
|
4916
|
+
* Every reader goes through this: rows created before #3626 carry no `kind`
|
|
4917
|
+
* attribute at all, and an absent one is `chat` everywhere — the detail route,
|
|
4918
|
+
* pull, diff, execution and codegen.
|
|
4919
|
+
*/
|
|
4920
|
+
export function promptKindOf(prompt) {
|
|
4921
|
+
const raw = prompt?.kind;
|
|
4922
|
+
if (typeof raw !== "string")
|
|
4923
|
+
return "chat";
|
|
4924
|
+
const text = raw.trim();
|
|
4925
|
+
return PROMPT_KINDS.includes(text)
|
|
4926
|
+
? text
|
|
4927
|
+
: "chat";
|
|
4928
|
+
}
|
|
4929
|
+
/** `null`, `undefined` and `""` are absence; `false` and `0` are values. */
|
|
4930
|
+
function isUnset(value) {
|
|
4931
|
+
if (value === null || value === undefined)
|
|
4932
|
+
return true;
|
|
4933
|
+
return typeof value === "string" && value.trim() === "";
|
|
4934
|
+
}
|
|
4935
|
+
function isPlainObject(value) {
|
|
4936
|
+
return (typeof value === "object" && value !== null && !Array.isArray(value));
|
|
4937
|
+
}
|
|
4938
|
+
function isNonEmptyString(value) {
|
|
4939
|
+
return typeof value === "string" && value.trim() !== "";
|
|
4940
|
+
}
|
|
4941
|
+
/**
|
|
4942
|
+
* `questions` as a value: a TOML table arrives as an object, a JSON-string
|
|
4943
|
+
* spelling as text (a `json` field takes either transport, #2880 criterion 5).
|
|
4944
|
+
* `undefined` for anything that is not a JSON object.
|
|
4945
|
+
*/
|
|
4946
|
+
function parseQuestions(value) {
|
|
4947
|
+
if (isPlainObject(value))
|
|
4948
|
+
return value;
|
|
4949
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
4950
|
+
try {
|
|
4951
|
+
const parsed = JSON.parse(value);
|
|
4952
|
+
return isPlainObject(parsed) ? parsed : undefined;
|
|
4953
|
+
}
|
|
4954
|
+
catch {
|
|
4955
|
+
return undefined;
|
|
4956
|
+
}
|
|
4957
|
+
}
|
|
4958
|
+
return undefined;
|
|
4959
|
+
}
|
|
4960
|
+
/** The chat keys this config actually sets, in declaration order. */
|
|
4961
|
+
function chatKeysSet(config) {
|
|
4962
|
+
return CHAT_CONFIG_KEYS.filter((key) => !isUnset(config[key]));
|
|
4963
|
+
}
|
|
4964
|
+
/**
|
|
4965
|
+
* One rule replaces the nine per-key refusals: a `[[configs]]` entry may carry
|
|
4966
|
+
* only the block named by its prompt's kind.
|
|
4967
|
+
*/
|
|
4968
|
+
function wrongBlockError(kind, offending) {
|
|
4969
|
+
const wrongKind = kind === "chat" ? "decisions" : "chat";
|
|
4970
|
+
return (`prompt kind is "${kind}", so a [[configs]] entry may carry only a ` +
|
|
4971
|
+
`[configs.${kind}] block; this one sets ${offending.join(", ")} ` +
|
|
4972
|
+
`([configs.${wrongKind}] in TOML). Move them to a ${wrongKind} prompt or ` +
|
|
4973
|
+
"remove them.");
|
|
4974
|
+
}
|
|
4975
|
+
function questionError(name, detail) {
|
|
4976
|
+
return `questions.${name} ${detail}`;
|
|
4977
|
+
}
|
|
4978
|
+
/** Every refusal a single decisions question can earn, or `null`. */
|
|
4979
|
+
function decisionsQuestionError(name, question) {
|
|
4980
|
+
if (!QUESTION_NAME.test(name)) {
|
|
4981
|
+
return (`questions.${name} is not a usable question name; names match ` +
|
|
4982
|
+
"[A-Za-z_][A-Za-z0-9_-]* (the endpoint keys its answers by them)");
|
|
4983
|
+
}
|
|
4984
|
+
if (!isPlainObject(question)) {
|
|
4985
|
+
return questionError(name, "must be a table declaring the question");
|
|
4986
|
+
}
|
|
4987
|
+
const type = question.type;
|
|
4988
|
+
if (typeof type !== "string" ||
|
|
4989
|
+
!DECISIONS_QUESTION_TYPES.includes(type)) {
|
|
4990
|
+
return questionError(name, `needs type = one of ${DECISIONS_QUESTION_TYPES.join(" | ")}`);
|
|
4991
|
+
}
|
|
4992
|
+
if (!isNonEmptyString(question.instructions)) {
|
|
4993
|
+
return questionError(name, "needs instructions — the sentence the model answers");
|
|
4994
|
+
}
|
|
4995
|
+
if (type === "choice") {
|
|
4996
|
+
const criteria = question.criteria;
|
|
4997
|
+
if (!isPlainObject(criteria)) {
|
|
4998
|
+
return questionError(name, "is a choice question, so it needs a criteria table of option key = " +
|
|
4999
|
+
"description");
|
|
5000
|
+
}
|
|
5001
|
+
const entries = Object.entries(criteria);
|
|
5002
|
+
if (entries.length < 2) {
|
|
5003
|
+
return questionError(name, "needs at least two criteria entries; a choice between one option is " +
|
|
5004
|
+
"not a choice");
|
|
5005
|
+
}
|
|
5006
|
+
const blank = entries.find(([, value]) => !isNonEmptyString(value));
|
|
5007
|
+
if (blank) {
|
|
5008
|
+
return questionError(name, `criteria.${blank[0]} needs a non-empty description`);
|
|
5009
|
+
}
|
|
5010
|
+
}
|
|
5011
|
+
// Every other key on a question — `score`'s array `criteria`, anything
|
|
5012
|
+
// TypeSafe adds next — passes through; the provider validates it.
|
|
5013
|
+
return null;
|
|
5014
|
+
}
|
|
5015
|
+
/**
|
|
5016
|
+
* The one rule: does this (kind, provider, config) triple describe something
|
|
5017
|
+
* the platform can run?
|
|
5018
|
+
*
|
|
5019
|
+
* `{ ok: true, kind: "chat" }` is today's behavior byte for byte. The decisions
|
|
5020
|
+
* arm returns the NORMALIZED questions so the caller stores one spelling
|
|
5021
|
+
* whichever transport the author used.
|
|
5022
|
+
*/
|
|
5023
|
+
export function resolvePromptKind(input) {
|
|
5024
|
+
const rawKind = input.kind;
|
|
5025
|
+
let kind = "chat";
|
|
5026
|
+
if (!isUnset(rawKind)) {
|
|
5027
|
+
const text = typeof rawKind === "string" ? rawKind.trim() : String(rawKind);
|
|
5028
|
+
if (!PROMPT_KINDS.includes(text)) {
|
|
5029
|
+
return {
|
|
5030
|
+
ok: false,
|
|
5031
|
+
error: `kind must be one of ${PROMPT_KINDS.join(" | ")}; found ` +
|
|
5032
|
+
`"${text}"`,
|
|
5033
|
+
};
|
|
5034
|
+
}
|
|
5035
|
+
kind = text;
|
|
5036
|
+
}
|
|
5037
|
+
const config = input.config || {};
|
|
5038
|
+
if (kind === "chat") {
|
|
5039
|
+
if (!isUnset(config.questions)) {
|
|
5040
|
+
return { ok: false, error: wrongBlockError("chat", ["questions"]) };
|
|
5041
|
+
}
|
|
5042
|
+
if (isUnset(config.userPromptTemplate)) {
|
|
5043
|
+
return { ok: false, error: "userPromptTemplate is required" };
|
|
5044
|
+
}
|
|
5045
|
+
return { ok: true, kind: "chat" };
|
|
5046
|
+
}
|
|
5047
|
+
// ── decisions ──────────────────────────────────────────────────────────
|
|
5048
|
+
const strayChatKeys = chatKeysSet(config);
|
|
5049
|
+
if (strayChatKeys.length > 0) {
|
|
5050
|
+
return { ok: false, error: wrongBlockError("decisions", strayChatKeys) };
|
|
5051
|
+
}
|
|
5052
|
+
const provider = typeof input.provider === "string" ? input.provider.trim().toLowerCase() : "";
|
|
5053
|
+
if (provider !== "openrouter") {
|
|
5054
|
+
return {
|
|
5055
|
+
ok: false,
|
|
5056
|
+
error: "a decisions config runs on the openrouter provider (OpenRouter's " +
|
|
5057
|
+
`/api/alpha/decisions endpoint); found "${provider || "none"}"`,
|
|
5058
|
+
};
|
|
5059
|
+
}
|
|
5060
|
+
const questions = parseQuestions(config.questions);
|
|
5061
|
+
if (!questions || Object.keys(questions).length === 0) {
|
|
5062
|
+
return {
|
|
5063
|
+
ok: false,
|
|
5064
|
+
error: "a decisions config needs questions — declare at least one under " +
|
|
5065
|
+
"[configs.decisions.questions.<name>]",
|
|
5066
|
+
};
|
|
5067
|
+
}
|
|
5068
|
+
for (const [name, question] of Object.entries(questions)) {
|
|
5069
|
+
const error = decisionsQuestionError(name, question);
|
|
5070
|
+
if (error)
|
|
5071
|
+
return { ok: false, error };
|
|
5072
|
+
}
|
|
5073
|
+
return { ok: true, kind: "decisions", questions };
|
|
5074
|
+
}
|
|
5075
|
+
/**
|
|
5076
|
+
* Move the deprecated flat chat keys of one `[[configs]]` entry under `chat`
|
|
5077
|
+
* — #3626 §4, the blob-bucket `accessPolicy` precedent made reusable.
|
|
5078
|
+
*
|
|
5079
|
+
* Push keeps accepting the nine keys flat so no existing file breaks, and pull
|
|
5080
|
+
* rewrites them, so one pull-then-push migrates a file. A key present in BOTH
|
|
5081
|
+
* spellings is not merged: the grouped value is left standing and the key is
|
|
5082
|
+
* reported in `conflicts`, which the caller turns into a refusal naming the
|
|
5083
|
+
* entry. Nothing else in the entry is touched — `providerConfig` is
|
|
5084
|
+
* provider-keyed rather than chat-keyed (D3626-002) and `questions` has no flat
|
|
5085
|
+
* spelling at all (SO3626-013).
|
|
5086
|
+
*/
|
|
5087
|
+
export function hoistDeprecatedChatKeys(entry) {
|
|
5088
|
+
if (!isPlainObject(entry)) {
|
|
5089
|
+
return { entry: {}, moved: [], conflicts: [] };
|
|
5090
|
+
}
|
|
5091
|
+
const out = { ...entry };
|
|
5092
|
+
const existingGroup = isPlainObject(out.chat) ? { ...out.chat } : undefined;
|
|
5093
|
+
const group = existingGroup ?? {};
|
|
5094
|
+
const moved = [];
|
|
5095
|
+
const conflicts = [];
|
|
5096
|
+
for (const key of CHAT_CONFIG_KEYS) {
|
|
5097
|
+
if (!(key in out))
|
|
5098
|
+
continue;
|
|
5099
|
+
if (existingGroup && key in existingGroup) {
|
|
5100
|
+
conflicts.push(key);
|
|
5101
|
+
delete out[key];
|
|
5102
|
+
continue;
|
|
5103
|
+
}
|
|
5104
|
+
group[key] = out[key];
|
|
5105
|
+
delete out[key];
|
|
5106
|
+
moved.push(key);
|
|
5107
|
+
}
|
|
5108
|
+
if (existingGroup || moved.length > 0)
|
|
5109
|
+
out.chat = group;
|
|
5110
|
+
return { entry: out, moved: moved.sort(), conflicts: conflicts.sort() };
|
|
5111
|
+
}
|
|
4773
5112
|
// ── src/config-surface/prompt.ts ─────────────────────────────────────────
|
|
4774
5113
|
/**
|
|
4775
5114
|
* The `prompt` configuration object's definition (issue #2644, phase 2).
|
|
@@ -4784,6 +5123,27 @@ function geminiResolution(model, effort, budget) {
|
|
|
4784
5123
|
* (`createPromptConfig` / `updatePromptConfig`) and pulls, not an opaque blob.
|
|
4785
5124
|
*/
|
|
4786
5125
|
const PROMPT_HANDLER = "src/admin-api.ts";
|
|
5126
|
+
/**
|
|
5127
|
+
* #3626 §4 — the nine chat keys, still accepted FLAT under `[[configs]]`.
|
|
5128
|
+
*
|
|
5129
|
+
* The blob-bucket `accessPolicy` precedent: declared `deprecated`, so
|
|
5130
|
+
* `config push` keeps taking a file written before the `[configs.chat]` block
|
|
5131
|
+
* existed, while `config pull` never writes the spelling again. One
|
|
5132
|
+
* pull-then-push migrates a file. `hoistDeprecatedChatKeys` moves them into the
|
|
5133
|
+
* group before the payload is built; rejecting the flat spelling outright is
|
|
5134
|
+
* #3642.
|
|
5135
|
+
*/
|
|
5136
|
+
const DEPRECATED_FLAT_CHAT_KEYS = Object.fromEntries(CHAT_CONFIG_KEYS.map((key) => [
|
|
5137
|
+
key,
|
|
5138
|
+
{
|
|
5139
|
+
kind: "deprecated",
|
|
5140
|
+
note: `#3626 — the pre-kind spelling of \`[configs.chat].${key}\`. Still ` +
|
|
5141
|
+
"accepted flat so no existing prompt file breaks (push hoists it " +
|
|
5142
|
+
"into the block and warns); `config pull` writes only the grouped " +
|
|
5143
|
+
"spelling, so one pull-then-push migrates the file. Rejecting the " +
|
|
5144
|
+
"flat spelling is #3642.",
|
|
5145
|
+
},
|
|
5146
|
+
]));
|
|
4787
5147
|
export const PROMPT_SURFACE = {
|
|
4788
5148
|
label: "prompt",
|
|
4789
5149
|
tables: [
|
|
@@ -4793,6 +5153,34 @@ export const PROMPT_SURFACE = {
|
|
|
4793
5153
|
model: "AppPrompt",
|
|
4794
5154
|
// Declaration order reproduces the pre-#2644 serializer's key order.
|
|
4795
5155
|
fields: [
|
|
5156
|
+
// #3626 — WHAT this prompt runs. `chat` is today's prompt (a chat
|
|
5157
|
+
// completion rendered from templates); `decisions` names OpenRouter's
|
|
5158
|
+
// `/api/alpha/decisions` endpoint, which takes a `state` value plus
|
|
5159
|
+
// named typed questions. It lives on the PROMPT because it decides the
|
|
5160
|
+
// input envelope, the output type, the meaning of `inputSchema` /
|
|
5161
|
+
// `outputSchema` and the shape of every test case's `inputVariables` —
|
|
5162
|
+
// all of which are asserted per prompt KEY.
|
|
5163
|
+
//
|
|
5164
|
+
// `emit: "always"` so a pulled file says which kind it is; an omitted
|
|
5165
|
+
// key is `chat`, so no file written before this changes meaning, and
|
|
5166
|
+
// `serverDefault` is what lets push send `chat` for an absent key and
|
|
5167
|
+
// `config diff` read both spellings as one value.
|
|
5168
|
+
//
|
|
5169
|
+
// CREATE_ONLY, and immutable in the strong sense (D3626-008): changing
|
|
5170
|
+
// a prompt's kind under its key would change the contract of every
|
|
5171
|
+
// caller compiled against it, so the update handler never reads it and
|
|
5172
|
+
// the push gate declines the change as `immutable` — the `ttlTier`
|
|
5173
|
+
// precedent. The prompt comparator projects `[prompt]` in CREATE mode
|
|
5174
|
+
// for exactly that reason (SO3626-008).
|
|
5175
|
+
{
|
|
5176
|
+
field: "kind",
|
|
5177
|
+
tomlKey: "kind",
|
|
5178
|
+
type: "string",
|
|
5179
|
+
emit: "always",
|
|
5180
|
+
writableOn: CREATE_ONLY,
|
|
5181
|
+
serverDefault: "chat",
|
|
5182
|
+
validation: handledBy("src/config-surface/prompt-kinds.ts", "resolvePromptKind", "apply"),
|
|
5183
|
+
},
|
|
4796
5184
|
{
|
|
4797
5185
|
field: "displayName",
|
|
4798
5186
|
tomlKey: "displayName",
|
|
@@ -4935,6 +5323,26 @@ export const PROMPT_SURFACE = {
|
|
|
4935
5323
|
tomlPath: ["configs"],
|
|
4936
5324
|
repeated: true,
|
|
4937
5325
|
model: "AppPromptConfig",
|
|
5326
|
+
// #3626 — one block per prompt kind. A `[[configs]]` entry carries the
|
|
5327
|
+
// SHARED keys flat (name, status, description, provider, model,
|
|
5328
|
+
// providerConfig, plus the `active` marker) and then exactly one block,
|
|
5329
|
+
// named after the prompt's kind. The rule that a config may carry only
|
|
5330
|
+
// its kind's block is `resolvePromptKind`; this declaration is what makes
|
|
5331
|
+
// the two blocks a property of the surface rather than of prompt-specific
|
|
5332
|
+
// code, so the next kind is a group plus its fields.
|
|
5333
|
+
tomlGroups: {
|
|
5334
|
+
chat: {
|
|
5335
|
+
note: "#3626 — the chat-completion settings of a `kind = \"chat\"` " +
|
|
5336
|
+
"prompt: the nine keys that describe a chat call and nothing " +
|
|
5337
|
+
"else. Authored under `[configs.chat]`; the flat spelling is " +
|
|
5338
|
+
"accepted as deprecated (#3642) and rewritten by `config pull`.",
|
|
5339
|
+
},
|
|
5340
|
+
decisions: {
|
|
5341
|
+
note: "#3626 — the settings of a `kind = \"decisions\"` prompt: the " +
|
|
5342
|
+
"named typed questions OpenRouter's /api/alpha/decisions endpoint " +
|
|
5343
|
+
"answers. Authored under `[configs.decisions.questions.<name>]`.",
|
|
5344
|
+
},
|
|
5345
|
+
},
|
|
4938
5346
|
fields: [
|
|
4939
5347
|
{
|
|
4940
5348
|
field: "configName",
|
|
@@ -4997,6 +5405,7 @@ export const PROMPT_SURFACE = {
|
|
|
4997
5405
|
{
|
|
4998
5406
|
field: "systemPrompt",
|
|
4999
5407
|
tomlKey: "systemPrompt",
|
|
5408
|
+
tomlGroup: "chat",
|
|
5000
5409
|
type: "string",
|
|
5001
5410
|
emit: "whenSet",
|
|
5002
5411
|
writableOn: BOTH,
|
|
@@ -5007,6 +5416,7 @@ export const PROMPT_SURFACE = {
|
|
|
5007
5416
|
{
|
|
5008
5417
|
field: "userPromptTemplate",
|
|
5009
5418
|
tomlKey: "userPromptTemplate",
|
|
5419
|
+
tomlGroup: "chat",
|
|
5010
5420
|
type: "string",
|
|
5011
5421
|
emit: "whenSet",
|
|
5012
5422
|
writableOn: BOTH,
|
|
@@ -5015,6 +5425,7 @@ export const PROMPT_SURFACE = {
|
|
|
5015
5425
|
{
|
|
5016
5426
|
field: "temperature",
|
|
5017
5427
|
tomlKey: "temperature",
|
|
5428
|
+
tomlGroup: "chat",
|
|
5018
5429
|
type: "number",
|
|
5019
5430
|
emit: "whenSet",
|
|
5020
5431
|
writableOn: BOTH,
|
|
@@ -5032,6 +5443,7 @@ export const PROMPT_SURFACE = {
|
|
|
5032
5443
|
{
|
|
5033
5444
|
field: "topP",
|
|
5034
5445
|
tomlKey: "topP",
|
|
5446
|
+
tomlGroup: "chat",
|
|
5035
5447
|
type: "number",
|
|
5036
5448
|
emit: "whenSet",
|
|
5037
5449
|
writableOn: BOTH,
|
|
@@ -5043,6 +5455,7 @@ export const PROMPT_SURFACE = {
|
|
|
5043
5455
|
{
|
|
5044
5456
|
field: "maxTokens",
|
|
5045
5457
|
tomlKey: "maxTokens",
|
|
5458
|
+
tomlGroup: "chat",
|
|
5046
5459
|
type: "number",
|
|
5047
5460
|
emit: "whenSet",
|
|
5048
5461
|
writableOn: BOTH,
|
|
@@ -5051,6 +5464,7 @@ export const PROMPT_SURFACE = {
|
|
|
5051
5464
|
{
|
|
5052
5465
|
field: "outputFormat",
|
|
5053
5466
|
tomlKey: "outputFormat",
|
|
5467
|
+
tomlGroup: "chat",
|
|
5054
5468
|
type: "string",
|
|
5055
5469
|
emit: "whenSet",
|
|
5056
5470
|
writableOn: BOTH,
|
|
@@ -5061,6 +5475,7 @@ export const PROMPT_SURFACE = {
|
|
|
5061
5475
|
{
|
|
5062
5476
|
field: "outputSchema",
|
|
5063
5477
|
tomlKey: "outputSchema",
|
|
5478
|
+
tomlGroup: "chat",
|
|
5064
5479
|
type: "json",
|
|
5065
5480
|
emit: "whenSet",
|
|
5066
5481
|
writableOn: BOTH,
|
|
@@ -5089,6 +5504,7 @@ export const PROMPT_SURFACE = {
|
|
|
5089
5504
|
{
|
|
5090
5505
|
field: "reasoningEffort",
|
|
5091
5506
|
tomlKey: "reasoningEffort",
|
|
5507
|
+
tomlGroup: "chat",
|
|
5092
5508
|
type: "string",
|
|
5093
5509
|
emit: "whenSet",
|
|
5094
5510
|
writableOn: BOTH,
|
|
@@ -5097,11 +5513,30 @@ export const PROMPT_SURFACE = {
|
|
|
5097
5513
|
{
|
|
5098
5514
|
field: "reasoningBudget",
|
|
5099
5515
|
tomlKey: "reasoningBudget",
|
|
5516
|
+
tomlGroup: "chat",
|
|
5100
5517
|
type: "number",
|
|
5101
5518
|
emit: "whenSet",
|
|
5102
5519
|
writableOn: BOTH,
|
|
5103
5520
|
validation: handledBy("src/config-surface/prompt-reasoning.ts", "resolvePromptReasoning", "apply"),
|
|
5104
5521
|
},
|
|
5522
|
+
// #3626 — the named typed questions a DECISIONS config asks. A `json`
|
|
5523
|
+
// field like `outputSchema`: stored as a JSON string, returned parsed,
|
|
5524
|
+
// and authored either as nested tables
|
|
5525
|
+
// (`[configs.decisions.questions.category]`) or as a JSON string.
|
|
5526
|
+
//
|
|
5527
|
+
// It has no flat spelling at all — `acceptedTomlKeys` refuses it at the
|
|
5528
|
+
// `[[configs]]` root and names the block it belongs under (SO3626-013),
|
|
5529
|
+
// because the payload builder reads a grouped field only from its
|
|
5530
|
+
// group and a flat value would otherwise be dropped in silence.
|
|
5531
|
+
{
|
|
5532
|
+
field: "questions",
|
|
5533
|
+
tomlKey: "questions",
|
|
5534
|
+
tomlGroup: "decisions",
|
|
5535
|
+
type: "json",
|
|
5536
|
+
emit: "whenSet",
|
|
5537
|
+
writableOn: BOTH,
|
|
5538
|
+
validation: handledBy("src/config-surface/prompt-kinds.ts", "resolvePromptKind", "apply"),
|
|
5539
|
+
},
|
|
5105
5540
|
],
|
|
5106
5541
|
notExposed: {
|
|
5107
5542
|
configId: {
|
|
@@ -5162,6 +5597,8 @@ export const PROMPT_SURFACE = {
|
|
|
5162
5597
|
"rejection does not fail a file that has always used it; pull " +
|
|
5163
5598
|
"never writes it.",
|
|
5164
5599
|
},
|
|
5600
|
+
// #3626 §4 — the nine chat keys in their pre-kind FLAT spelling.
|
|
5601
|
+
...DEPRECATED_FLAT_CHAT_KEYS,
|
|
5165
5602
|
},
|
|
5166
5603
|
responseOnlyKeys: {},
|
|
5167
5604
|
},
|
|
@@ -8138,6 +8575,7 @@ export const GENERATED_CONFIG_MODEL_FIELDS = {
|
|
|
8138
8575
|
"promptId",
|
|
8139
8576
|
"appId",
|
|
8140
8577
|
"promptKey",
|
|
8578
|
+
"kind",
|
|
8141
8579
|
"displayName",
|
|
8142
8580
|
"description",
|
|
8143
8581
|
"copiedFromCatalogId",
|
|
@@ -8170,6 +8608,7 @@ export const GENERATED_CONFIG_MODEL_FIELDS = {
|
|
|
8170
8608
|
"outputFormat",
|
|
8171
8609
|
"outputSchema",
|
|
8172
8610
|
"providerConfig",
|
|
8611
|
+
"questions",
|
|
8173
8612
|
"reasoningEffort",
|
|
8174
8613
|
"reasoningBudget",
|
|
8175
8614
|
"status",
|