922a57fe34
Self-hosted 開源:WASM 零件 + recipe + cypher-executor,跑在你自己的 Cloudflare。 此為重建的乾淨歷史起點(移除曾誤 commit 的 GCP SA 金鑰,舊歷史保留在 richblack/arcrun 與本地 backup 分支)。含: - acr init --self-hosted installer(建 KV/R2 + codeload 拉預編譯 wasm + wrangler deploy + seed recipe) - recipe push 把關(資料外流提醒 + 打通檢查) - 19 個正當零件預編譯 wasm(claude_api/km_writer/kbdb_upsert_block 排除:違反 DECISIONS §1) - CLI / cypher-executor / registry / 完整 SDD Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
91 lines
4.5 KiB
TypeScript
91 lines
4.5 KiB
TypeScript
/**
|
||
* prompt_recipe Zod schema
|
||
* SDD: matrix/arcrun/.agents/specs/recipe-system/design.md §2.1
|
||
*
|
||
* 平行於既有 auth_recipe / api_recipe,存 RECIPES KV (key: `prompt_recipe:{name}`)
|
||
* 容器 + recipe 模式:claude_api 是容器,recipe 是配方
|
||
*/
|
||
|
||
import { z } from 'zod';
|
||
|
||
// ── Transform 白名單 ──────────────────────────────────────────────────────────
|
||
// 限制 transform 種類避免變 mini-DSL;超過範圍請寫零件
|
||
export const TRANSFORM_NAMES = [
|
||
'json_array', // array → JSON.stringify 整體
|
||
'to_string', // 任意值 → String(x)
|
||
'join', // array → join(sep),sep 預設換行
|
||
'markdown_list', // array → "- a\n- b\n- c"
|
||
'extract_field', // array of object → 抽 field 後的 array(再可串其他 transform)
|
||
'first', // array → first element(取單一)
|
||
'pluck_content', // KBDB blocks array → 抽 content 後 join 雙換行(草稿合併常用)
|
||
] as const;
|
||
|
||
/** transform 表示法:name 或 name:arg(如 extract_field:page_name) */
|
||
export const TransformSchema = z.string().regex(/^[a-z_]+(:.+)?$/, 'transform 必須為 name 或 name:arg 格式');
|
||
|
||
// ── Fragment:從 KBDB / KV 抓固定資料 ──────────────────────────────────────────
|
||
export const KBDBBlockFragmentSchema = z.object({
|
||
var: z.string().min(1), // prompt template 內的變數名
|
||
source: z.literal('kbdb_block'),
|
||
block_id: z.string().optional(), // 二擇一
|
||
block_page_name: z.string().optional(), // 比 block_id 穩定
|
||
field: z.string().default('content'), // 抓 block 的哪個欄位
|
||
});
|
||
|
||
export const KVFragmentSchema = z.object({
|
||
var: z.string().min(1),
|
||
source: z.literal('kv'),
|
||
key: z.string().min(1),
|
||
});
|
||
|
||
// discriminatedUnion 對 refined zod object 不支援,故拆成驗證後 + 單獨檢查 block_id|page_name
|
||
export const FragmentSchema = z.discriminatedUnion('source', [
|
||
KBDBBlockFragmentSchema,
|
||
KVFragmentSchema,
|
||
]).superRefine((d, ctx) => {
|
||
if (d.source === 'kbdb_block' && !d.block_id && !d.block_page_name) {
|
||
ctx.addIssue({
|
||
code: z.ZodIssueCode.custom,
|
||
message: 'block_id 或 block_page_name 必填其一',
|
||
});
|
||
}
|
||
});
|
||
|
||
// ── Input:從 workflow context 取值(含 transform) ────────────────────────────
|
||
export const InputSchema = z.object({
|
||
var: z.string().min(1),
|
||
from: z.string().min(1), // JSONPath-lite,如 "ctx.read_drafts.blocks"
|
||
transform: TransformSchema.optional(),
|
||
default: z.unknown().optional(), // from 取不到時的預設值(避免炸 prompt)
|
||
});
|
||
|
||
// ── Prompt 組裝 ──────────────────────────────────────────────────────────────
|
||
export const PromptAssemblySchema = z.object({
|
||
system: z.string().min(1), // 模板,可含 {{var}}
|
||
user: z.string().min(1),
|
||
});
|
||
|
||
// ── 輸出規格 ──────────────────────────────────────────────────────────────────
|
||
export const OutputSpecSchema = z.object({
|
||
format: z.enum(['text', 'json']).default('text'),
|
||
// 若 format=json,可選 schema 做 parse 後驗證(簡化版,列必填欄位即可)
|
||
required_fields: z.array(z.string()).optional(),
|
||
});
|
||
|
||
// ── 完整 prompt_recipe 定義 ────────────────────────────────────────────────────
|
||
export const PromptRecipeSchema = z.object({
|
||
kind: z.literal('prompt_recipe'),
|
||
name: z.string().min(1).regex(/^[a-z][a-z0-9_]*$/, 'name 為 lowercase + underscore'),
|
||
version: z.number().int().positive().default(1),
|
||
description: z.string().optional(),
|
||
model: z.enum(['haiku', 'sonnet', 'opus']).default('sonnet'),
|
||
fragments: z.array(FragmentSchema).default([]),
|
||
inputs: z.array(InputSchema).default([]),
|
||
prompt_assembly: PromptAssemblySchema,
|
||
output: OutputSpecSchema.default({ format: 'text' }),
|
||
});
|
||
|
||
export type PromptRecipe = z.infer<typeof PromptRecipeSchema>;
|
||
export type Fragment = z.infer<typeof FragmentSchema>;
|
||
export type RecipeInput = z.infer<typeof InputSchema>;
|