types.ts 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708
  1. import { z } from "zod";
  2. // ============================================================================
  3. // Tool Access Schema
  4. // ============================================================================
  5. /**
  6. * Defines which tools an agent has access to.
  7. * Each tool can be enabled/disabled via boolean flags.
  8. */
  9. export const ToolAccessSchema = z.object({
  10. read: z.boolean().optional(),
  11. write: z.boolean().optional(),
  12. edit: z.boolean().optional(),
  13. bash: z.boolean().optional(),
  14. task: z.boolean().optional(),
  15. grep: z.boolean().optional(),
  16. glob: z.boolean().optional(),
  17. patch: z.boolean().optional(),
  18. question: z.boolean().optional(),
  19. });
  20. // ============================================================================
  21. // Permission Schemas
  22. // ============================================================================
  23. /**
  24. * A single permission decision.
  25. *
  26. * Field name rationale: `action` mirrors OpenCode's own runtime rule shape
  27. * (`{ permission, pattern, action }`), verified against the installed resolver in
  28. * `docs/architecture/canonical-refactor/10-PRECEDENCE-EXPERIMENT.md` §7.
  29. */
  30. export const PermissionActionSchema = z.enum(["allow", "deny", "ask"]);
  31. /**
  32. * Permission rules can be:
  33. * - A literal: "allow", "deny", "ask"
  34. * - A boolean (true = allow, false = deny)
  35. * - A record mapping specific operations to permission literals
  36. *
  37. * This is the *authored* (on-disk YAML) form. It is sugar over the canonical
  38. * ordered form below — see {@link desugarPermission}.
  39. */
  40. export const PermissionRuleSchema = z.union([
  41. z.literal("allow"),
  42. z.literal("deny"),
  43. z.literal("ask"),
  44. z.boolean(),
  45. z.record(z.string(), PermissionActionSchema),
  46. ]);
  47. /**
  48. * The legacy/authored `permission:` mapping exactly as OpenCode accepts it on disk:
  49. * capability name -> rule. This is what {@link AgentFrontmatterSchema} still carries,
  50. * so existing agent files keep parsing unchanged.
  51. *
  52. * ⚠️ A JS object is an UNORDERED map as far as any schema is concerned. This shape is
  53. * accepted as INPUT only; the canonical representation is {@link GranularPermissionSchema}.
  54. */
  55. export const PermissionMapSchema = z.record(z.string(), PermissionRuleSchema);
  56. /**
  57. * One ordered rule within a capability. `pattern` is a glob whose namespace depends on
  58. * the capability (path glob for read/write/edit, command glob for bash, agent id for task).
  59. */
  60. export const PermissionRuleEntrySchema = z
  61. .object({
  62. pattern: z.string().min(1),
  63. action: PermissionActionSchema,
  64. })
  65. .strict();
  66. /**
  67. * Ordered rules for a single capability. **Array order is semantic**: rules are evaluated
  68. * in authored order and the LAST matching rule wins.
  69. *
  70. * Duplicate patterns are representable here by design — the OpenCode serializer, not the
  71. * schema, is responsible for refusing to emit them (the map format cannot round-trip them).
  72. */
  73. export const PermissionRuleListSchema = z.array(PermissionRuleEntrySchema);
  74. /**
  75. * One capability's ordered rule list. Capability entries are themselves ordered, because
  76. * OpenCode flattens the capability map into a single rule list before resolving and
  77. * wildcard capability keys (e.g. `"*"`) can match alongside specific ones.
  78. */
  79. export const GranularPermissionEntrySchema = z
  80. .object({
  81. capability: z.string().min(1),
  82. rules: PermissionRuleListSchema,
  83. })
  84. .strict();
  85. /**
  86. * Granular permissions in their canonical, ORDER-PRESERVING representation.
  87. *
  88. * This deliberately replaces the previous `z.record(...)` map. Last-match-wins precedence
  89. * is meaningless without a guaranteed order, and a record only preserved order by accident
  90. * of ECMAScript string-key insertion ordering — an accident that demonstrably breaks for
  91. * integer-like keys (see {@link desugarPermission}).
  92. *
  93. * Semantics (confirmed live against OpenCode 1.17.20 —
  94. * `docs/architecture/canonical-refactor/10-PRECEDENCE-EXPERIMENT.md`):
  95. * flatten entries in order, then resolve with **last-match-wins** (`Array.findLast`).
  96. */
  97. export const GranularPermissionSchema = z.array(GranularPermissionEntrySchema);
  98. /** Scopes that ECMAScript would silently reorder to the front of an object's key list. */
  99. const INTEGER_LIKE_SCOPE = /^\d+$/;
  100. /**
  101. * What an author may write under `permission:` — either the canonical ordered form or the
  102. * OpenCode map sugar. Desugars to the canonical ordered form, preserving source order.
  103. */
  104. export const PermissionInputSchema = z
  105. .union([GranularPermissionSchema, PermissionMapSchema])
  106. .transform((input, ctx) => desugar(input, ctx));
  107. // ----------------------------------------------------------------------------
  108. // Permission desugaring
  109. // ----------------------------------------------------------------------------
  110. type PermissionRuleInput = z.infer<typeof PermissionRuleSchema>;
  111. type PermissionRuleEntryOut = z.infer<typeof PermissionRuleEntrySchema>;
  112. type GranularPermissionOut = z.infer<typeof GranularPermissionSchema>;
  113. /** Reject scopes ECMAScript key ordering would silently move, invalidating rule order. */
  114. function reject(scope: string, ctx: z.RefinementCtx, path: (string | number)[]): boolean {
  115. if (!INTEGER_LIKE_SCOPE.test(scope.trim())) return false;
  116. ctx.addIssue({
  117. code: z.ZodIssueCode.custom,
  118. path,
  119. message:
  120. `integer-like scope "${scope}" is not allowed: ECMAScript reorders integer-like ` +
  121. `object keys to the front, which silently changes last-match-wins precedence`,
  122. });
  123. return true;
  124. }
  125. /** Expand one authored rule (scalar, boolean or scope map) into ordered rule entries. */
  126. function expand(
  127. rule: PermissionRuleInput,
  128. ctx: z.RefinementCtx,
  129. path: (string | number)[]
  130. ): PermissionRuleEntryOut[] {
  131. if (typeof rule === "boolean") {
  132. return [{ pattern: "*", action: rule ? "allow" : "deny" }];
  133. }
  134. if (typeof rule === "string") {
  135. return [{ pattern: "*", action: rule }];
  136. }
  137. return Object.entries(rule).flatMap(([pattern, action]) =>
  138. reject(pattern, ctx, [...path, pattern]) ? [] : [{ pattern, action }]
  139. );
  140. }
  141. /**
  142. * Back-compat parser: converts authored `permission:` input into the canonical ordered
  143. * form **in source order**.
  144. *
  145. * - `edit: deny` -> [{ capability: "edit", rules: [{ pattern: "*", action: "deny" }] }]
  146. * - `bash: { "*": deny, "ls*": allow }` -> rules in exactly that order (the later rule wins)
  147. * - already-ordered input -> identity
  148. */
  149. function desugar(
  150. input: GranularPermissionOut | Record<string, PermissionRuleInput>,
  151. ctx: z.RefinementCtx
  152. ): GranularPermissionOut {
  153. if (Array.isArray(input)) return input;
  154. return Object.entries(input).flatMap(([capability, rule]) =>
  155. reject(capability, ctx, [capability])
  156. ? []
  157. : [{ capability, rules: expand(rule, ctx, [capability]) }]
  158. );
  159. }
  160. /**
  161. * Desugar authored permission input into the canonical ordered form.
  162. * Throws a `ZodError` on integer-like scopes or malformed input.
  163. */
  164. export function desugarPermission(input: unknown): GranularPermissionOut {
  165. return PermissionInputSchema.parse(input);
  166. }
  167. // ============================================================================
  168. // Context Schemas
  169. // ============================================================================
  170. /**
  171. * Context priority levels for loading order and importance.
  172. */
  173. export const ContextPrioritySchema = z.enum(["critical", "high", "medium", "low"]);
  174. /**
  175. * References a context file with optional priority and description.
  176. */
  177. export const ContextReferenceSchema = z.object({
  178. path: z.string(),
  179. priority: ContextPrioritySchema.optional(),
  180. description: z.string().optional(),
  181. });
  182. // ============================================================================
  183. // Dependency Schema
  184. // ============================================================================
  185. /**
  186. * References external dependencies like subagents, contexts, commands, skills, or tools.
  187. */
  188. export const DependencyReferenceSchema = z.object({
  189. type: z.enum(["subagent", "context", "command", "skill", "tool"]),
  190. id: z.string(),
  191. });
  192. // ============================================================================
  193. // Agent Configuration Schemas
  194. // ============================================================================
  195. /**
  196. * Defines the operational mode of an agent.
  197. */
  198. export const AgentModeSchema = z.enum(["primary", "subagent", "all"]);
  199. /**
  200. * Agent categories for organizational purposes.
  201. */
  202. export const AgentCategorySchema = z.enum([
  203. "core",
  204. "development",
  205. "content",
  206. "data",
  207. "product",
  208. "learning",
  209. "meta",
  210. "specialist",
  211. ]);
  212. /**
  213. * Defines whether this is a primary agent or subagent.
  214. */
  215. export const AgentTypeSchema = z.enum(["agent", "subagent"]);
  216. // ============================================================================
  217. // Model Configuration Schemas
  218. // ============================================================================
  219. /**
  220. * Model identifier - can be any string representing a model name or ID.
  221. */
  222. export const ModelIdentifierSchema = z.union([z.string(), z.string()]);
  223. /**
  224. * Temperature parameter for model inference (typically 0.0 to 2.0).
  225. */
  226. export const TemperatureSchema = z.number();
  227. // ============================================================================
  228. // Skill Schema
  229. // ============================================================================
  230. /**
  231. * Skill reference can be:
  232. * - A simple string (skill name)
  233. * - An object with name and optional configuration
  234. */
  235. export const SkillReferenceSchema = z.union([
  236. z.string(),
  237. z.object({
  238. name: z.string(),
  239. config: z.record(z.string(), z.any()).optional(),
  240. }),
  241. ]);
  242. // ============================================================================
  243. // Hook Schemas
  244. // ============================================================================
  245. /**
  246. * Events that can trigger hooks during agent execution.
  247. */
  248. export const HookEventSchema = z.enum([
  249. "PreToolUse",
  250. "PostToolUse",
  251. "PermissionRequest",
  252. "AgentStart",
  253. "AgentEnd",
  254. ]);
  255. /**
  256. * Defines a hook that executes commands in response to specific events.
  257. */
  258. export const HookDefinitionSchema = z.object({
  259. event: HookEventSchema,
  260. matchers: z.array(z.string()).optional(),
  261. commands: z.array(
  262. z.object({
  263. type: z.literal("command"),
  264. command: z.string(),
  265. })
  266. ),
  267. });
  268. // ============================================================================
  269. // Agent Frontmatter Schema
  270. // ============================================================================
  271. /**
  272. * Agent frontmatter contains the primary configuration defined in the agent's
  273. * markdown file header (YAML frontmatter).
  274. */
  275. export const AgentFrontmatterSchema = z.object({
  276. name: z.string(),
  277. description: z.string(),
  278. mode: AgentModeSchema,
  279. temperature: TemperatureSchema.optional(),
  280. model: ModelIdentifierSchema.optional(),
  281. maxSteps: z.number().optional(),
  282. disable: z.boolean().optional(),
  283. hidden: z.boolean().optional(),
  284. prompt: z.string().optional(),
  285. tools: ToolAccessSchema.optional(),
  286. permission: PermissionMapSchema.optional(),
  287. skills: z.array(SkillReferenceSchema).optional(),
  288. hooks: z.array(HookDefinitionSchema).optional(),
  289. });
  290. // ============================================================================
  291. // Canonical `oac:` Frontmatter Block
  292. // ============================================================================
  293. /**
  294. * Stable machine identity. Kebab-case slug — verified against all 28 entries in
  295. * `.opencode/config/agent-metadata.json`.
  296. */
  297. export const OacIdSchema = z
  298. .string()
  299. .regex(
  300. /^[a-z0-9]+(?:-[a-z0-9]+)*$/,
  301. "id must be kebab-case: lowercase alphanumeric words joined by single hyphens"
  302. );
  303. /** SemVer of the authored component. Corpus uses 1.0.0 / 2.0.0. */
  304. export const OacVersionSchema = z
  305. .string()
  306. .regex(/^\d+\.\d+\.\d+$/, "version must be SemVer (MAJOR.MINOR.PATCH)");
  307. const CATEGORY_ROOTS: readonly string[] = [
  308. ...AgentCategorySchema.options,
  309. // Present in the real corpus but absent from AgentCategorySchema:
  310. "subagents", // 20 entries: subagents/{code,core,development,system-builder,test,utils}
  311. "testing", // 1 entry: eval-runner
  312. ];
  313. const CATEGORY_SEGMENT = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
  314. /**
  315. * Organizational category. Mirrors the agent's directory under `.opencode/agent/`:
  316. * a closed root vocabulary, optionally followed by one `/`-joined sub-segment.
  317. *
  318. * ⚠️ Deliberately a superset of {@link AgentCategorySchema}, which cannot express the
  319. * corpus: 21 of the 28 entries in `.opencode/config/agent-metadata.json` use values that
  320. * enum rejects (`subagents/core`, `subagents/code`, …, `testing`). The ratified
  321. * "the schema must accept its own corpus" rule (02-canonical-schema.md v3) wins over the
  322. * brief's "reuses AgentCategorySchema". The root stays closed so typos are still caught.
  323. */
  324. export const OacCategorySchema = z.string().refine(
  325. (value) => {
  326. const segments = value.split("/");
  327. if (segments.length > 2) return false;
  328. const [root = "", sub] = segments;
  329. if (!CATEGORY_ROOTS.includes(root)) return false;
  330. return sub === undefined || CATEGORY_SEGMENT.test(sub);
  331. },
  332. {
  333. message:
  334. `category must be "<root>" or "<root>/<segment>" where root is one of: ` +
  335. CATEGORY_ROOTS.join(", "),
  336. }
  337. );
  338. /**
  339. * Platforms a component can declare as an emit target. Every value here has a
  340. * working adapter; which of them `oac build` actually wires up is a separate,
  341. * narrower question — see the build command's target registry.
  342. */
  343. export const BuildTargetSchema = z.enum([
  344. "opencode",
  345. "claude-code",
  346. "cursor",
  347. "windsurf",
  348. ]);
  349. /**
  350. * Which platforms this component is emitted to. At least one target is required;
  351. * `targets: []` is rejected because a component that emits nowhere is dead weight the
  352. * build would silently skip. Defaults to `["opencode"]` — true of all 34 agents on disk —
  353. * so an `agent-metadata.json` entry validates as an `oac:` block verbatim.
  354. */
  355. export const BuildTargetsSchema = z
  356. .array(BuildTargetSchema)
  357. .min(1, "targets must list at least one build target")
  358. .default(["opencode"]);
  359. /**
  360. * A dependency reference in either authored form:
  361. * - the flat typed string the corpus uses today (`"subagent:tester"`, `"context:standards-code"`)
  362. * - the structured {@link DependencyReferenceSchema} form
  363. *
  364. * Both normalize to `{ type, id }`, so `.opencode/config/agent-metadata.json` round-trips
  365. * byte-for-byte with zero migration.
  366. */
  367. export const DependencyRefInputSchema = z.union([
  368. z.string().transform((value, ctx): z.infer<typeof DependencyReferenceSchema> => {
  369. const separator = value.indexOf(":");
  370. const parsed = DependencyReferenceSchema.safeParse({
  371. type: value.slice(0, separator),
  372. id: value.slice(separator + 1),
  373. });
  374. if (separator <= 0 || !parsed.success) {
  375. ctx.addIssue({
  376. code: z.ZodIssueCode.custom,
  377. message:
  378. `dependency "${value}" must be "<type>:<id>" where type is one of: ` +
  379. DependencyReferenceSchema.shape.type.options.join(", "),
  380. });
  381. return z.NEVER;
  382. }
  383. return parsed.data;
  384. }),
  385. DependencyReferenceSchema,
  386. ]);
  387. /**
  388. * What one target may override about this component, authored by a human.
  389. *
  390. * ## Why overrides exist at all
  391. *
  392. * A canonical `permission:` block is an *enforcement* spec: ordered globs, last-match-wins.
  393. * Some targets cannot enforce that. Claude Code is the live example — verified against its
  394. * docs on 2026-07-15:
  395. *
  396. * - subagent frontmatter carries `tools:`/`disallowedTools:` and nothing else
  397. * (`sub-agents.md`, "Supported frontmatter fields");
  398. * - permission RULES exist, but only in `settings.json`, and they "apply to the entire
  399. * session, not only the plugin subagent" (`sub-agents.md`) — there is no per-agent scope;
  400. * - a plugin's own `settings.json` supports only the `agent` and `subagentStatusLine` keys
  401. * (`plugins-reference.md`), so a plugin cannot ship permission rules even session-wide;
  402. * - precedence is category-based (deny → ask → allow, specificity-blind, `permissions.md`),
  403. * which cannot express last-match-wins even in principle.
  404. *
  405. * So for an agent whose canonical rules are scoped, *no emission is both faithful and useful*.
  406. * Fail-closed yields a documentation scout that cannot read; widening is how the shipped
  407. * agents came to leak. That is not a question an adapter can answer — it is a security
  408. * decision. This block is where a human answers it, once, in the source, visible in a diff.
  409. *
  410. * @see {@link TargetOverridesSchema} for the rule that makes a widening un-silenceable.
  411. */
  412. export const TargetOverrideSchema = z
  413. .object({
  414. /** Component name on this target. Defaults to {@link OacBlockSchema}'s `id`. */
  415. name: z.string().min(1).optional(),
  416. /** Target-native model id (e.g. Claude Code's `sonnet`), distinct from OpenCode's. */
  417. model: z.string().min(1).optional(),
  418. /**
  419. * The tools this component is granted on this target, in the target's own vocabulary.
  420. * Deliberately `string[]`, not an enum: each target names its tools differently, and the
  421. * adapter that owns those names validates them. A schema-level enum here would make
  422. * `types.ts` know about every target's tool list.
  423. */
  424. tools: z.array(z.string().min(1)).optional(),
  425. /**
  426. * Why it is acceptable that this target will not enforce a capability's canonical scope,
  427. * keyed by capability (`bash`, `edit`, …).
  428. *
  429. * This is the honest field, and the one that keeps the whole mechanism from rotting. When
  430. * an override grants a tool whose canonical rules are scoped, the scope is simply not
  431. * applied on the target — it survives as prompt text at best. That is a real widening, and
  432. * the author is asserting it is acceptable.
  433. *
  434. * Keyed rather than free-form prose **so the adapter can check it**: every granted-but-
  435. * scoped capability must have an entry, and every entry must correspond to one. A prose
  436. * blob would decay into a rubber stamp that nothing verifies; this cannot silently fall
  437. * out of date, because the build fails when it does.
  438. */
  439. unenforced: z.record(z.string().min(1), z.string().min(1)).default({}),
  440. })
  441. .strict();
  442. /**
  443. * Per-target overrides, keyed by target.
  444. *
  445. * A closed object rather than `z.record(BuildTargetSchema, …)` so a typo'd or unknown target
  446. * key is a parse error instead of a silently-ignored block that never takes effect.
  447. */
  448. export const TargetOverridesSchema = z
  449. .object({
  450. opencode: TargetOverrideSchema.optional(),
  451. "claude-code": TargetOverrideSchema.optional(),
  452. cursor: TargetOverrideSchema.optional(),
  453. windsurf: TargetOverrideSchema.optional(),
  454. })
  455. .strict();
  456. /**
  457. * The canonical `oac:` frontmatter block — everything a component needs that OpenCode's
  458. * frontmatter schema rejects as an unknown field. This is precisely the content of
  459. * `.opencode/config/agent-metadata.json`; carrying it here is what lets that sidecar be
  460. * dissolved. `oac build` strips this block when emitting OpenCode agent files.
  461. *
  462. * Strict: an unknown key is an error, never silently dropped.
  463. */
  464. export const OacBlockFieldsSchema = z
  465. .object({
  466. id: OacIdSchema,
  467. name: z.string().min(1),
  468. category: OacCategorySchema,
  469. type: AgentTypeSchema,
  470. version: OacVersionSchema.default("1.0.0"),
  471. author: z.string().min(1).default("opencode"),
  472. tags: z.array(z.string()).default([]),
  473. dependencies: z.array(DependencyRefInputSchema).default([]),
  474. targets: BuildTargetsSchema,
  475. overrides: TargetOverridesSchema.default({}),
  476. })
  477. .strict();
  478. /**
  479. * The `oac:` block as parsed. {@link OacBlockFieldsSchema} plus the cross-field checks.
  480. *
  481. * This is a `ZodEffects`, so it has no `.shape`; reach for {@link OacBlockFieldsSchema} when
  482. * you need the field list itself.
  483. */
  484. export const OacBlockSchema = OacBlockFieldsSchema
  485. .superRefine((oac, ctx) => {
  486. // An override for a target this component does not emit to is dead config: it looks like
  487. // it is doing something and never runs. Almost always a half-finished edit to `targets`.
  488. for (const target of Object.keys(oac.overrides)) {
  489. if (!oac.targets.includes(target as z.infer<typeof BuildTargetSchema>)) {
  490. ctx.addIssue({
  491. code: z.ZodIssueCode.custom,
  492. path: ["overrides", target],
  493. message:
  494. `override declared for target "${target}", which is not in targets ` +
  495. `[${oac.targets.join(", ")}] — it would never be applied`,
  496. });
  497. }
  498. }
  499. });
  500. // ============================================================================
  501. // Canonical Agent Schema
  502. // ============================================================================
  503. /**
  504. * A canonical agent file: OpenCode-legal frontmatter PLUS the `oac:` block. One file
  505. * fully defines one component — no sidecar, no second source of truth.
  506. *
  507. * `permission` accepts the authored OpenCode map sugar and desugars it into the ordered
  508. * {@link GranularPermissionSchema} form, in source order.
  509. */
  510. export const CanonicalAgentSchema = AgentFrontmatterSchema.extend({
  511. oac: OacBlockSchema,
  512. permission: PermissionInputSchema.optional(),
  513. });
  514. // ============================================================================
  515. // Agent Metadata Schema
  516. // ============================================================================
  517. /**
  518. * Agent metadata contains identification and organizational information.
  519. * Stored separately from frontmatter in agent-metadata.json.
  520. */
  521. export const AgentMetadataSchema = z.object({
  522. id: z.string(),
  523. name: z.string(),
  524. category: AgentCategorySchema,
  525. type: AgentTypeSchema,
  526. version: z.string(),
  527. author: z.string(),
  528. tags: z.array(z.string()).optional().default([]),
  529. dependencies: z.array(DependencyReferenceSchema).optional().default([]),
  530. });
  531. // ============================================================================
  532. // OpenAgent Schema
  533. // ============================================================================
  534. /**
  535. * Complete OpenAgent schema combining frontmatter, metadata, system prompt,
  536. * contexts, and optional sections.
  537. *
  538. * This represents the full agent definition after parsing and merging all
  539. * configuration sources.
  540. */
  541. export const OpenAgentSchema = z.object({
  542. frontmatter: AgentFrontmatterSchema,
  543. metadata: z.object({
  544. id: z.string().optional(),
  545. name: z.string().optional(),
  546. category: AgentCategorySchema.optional(),
  547. type: AgentTypeSchema.optional(),
  548. version: z.string().optional(),
  549. author: z.string().optional(),
  550. tags: z.array(z.string()).optional().default([]).optional(),
  551. dependencies: z.array(DependencyReferenceSchema).optional().default([]).optional(),
  552. }),
  553. systemPrompt: z.string(),
  554. contexts: z.array(ContextReferenceSchema).optional().default([]),
  555. sections: z.object({
  556. skills: z.array(z.string()).optional().default([]),
  557. examples: z.array(z.string()).optional().default([]),
  558. commands: z.array(z.string()).optional().default([]),
  559. workflow: z.string().optional(),
  560. }).optional(),
  561. });
  562. // ============================================================================
  563. // Tool Configuration Schema
  564. // ============================================================================
  565. /**
  566. * Configuration file output for external tools.
  567. * Contains the file name, content, and encoding format.
  568. */
  569. export const ToolConfigSchema = z.object({
  570. fileName: z.string(),
  571. content: z.string(),
  572. encoding: z.enum(["utf-8", "base64"]).optional().default("utf-8"),
  573. });
  574. // ============================================================================
  575. // Type Exports (z.infer)
  576. // ============================================================================
  577. export type ToolAccess = z.infer<typeof ToolAccessSchema>;
  578. export type PermissionAction = z.infer<typeof PermissionActionSchema>;
  579. export type PermissionRule = z.infer<typeof PermissionRuleSchema>;
  580. /** The authored (OpenCode on-disk) permission map. Unordered — input only. */
  581. export type PermissionMap = z.infer<typeof PermissionMapSchema>;
  582. export type PermissionRuleEntry = z.infer<typeof PermissionRuleEntrySchema>;
  583. export type PermissionRuleList = z.infer<typeof PermissionRuleListSchema>;
  584. export type GranularPermissionEntry = z.infer<typeof GranularPermissionEntrySchema>;
  585. /** Canonical ORDERED granular permissions. Array order is semantic (last-match-wins). */
  586. export type GranularPermission = z.infer<typeof GranularPermissionSchema>;
  587. /** Authored permission input, before desugaring. */
  588. export type PermissionInput = z.input<typeof PermissionInputSchema>;
  589. export type ContextPriority = z.infer<typeof ContextPrioritySchema>;
  590. export type ContextReference = z.infer<typeof ContextReferenceSchema>;
  591. export type DependencyReference = z.infer<typeof DependencyReferenceSchema>;
  592. export type AgentMode = z.infer<typeof AgentModeSchema>;
  593. export type AgentCategory = z.infer<typeof AgentCategorySchema>;
  594. export type AgentType = z.infer<typeof AgentTypeSchema>;
  595. export type ModelIdentifier = z.infer<typeof ModelIdentifierSchema>;
  596. export type Temperature = z.infer<typeof TemperatureSchema>;
  597. export type SkillReference = z.infer<typeof SkillReferenceSchema>;
  598. export type HookEvent = z.infer<typeof HookEventSchema>;
  599. export type HookDefinition = z.infer<typeof HookDefinitionSchema>;
  600. export type AgentFrontmatter = z.infer<typeof AgentFrontmatterSchema>;
  601. export type OacId = z.infer<typeof OacIdSchema>;
  602. export type OacCategory = z.infer<typeof OacCategorySchema>;
  603. export type BuildTarget = z.infer<typeof BuildTargetSchema>;
  604. export type TargetOverride = z.infer<typeof TargetOverrideSchema>;
  605. export type TargetOverrides = z.infer<typeof TargetOverridesSchema>;
  606. export type OacBlock = z.infer<typeof OacBlockSchema>;
  607. /** Authored `oac:` block, before defaults are applied. */
  608. export type OacBlockInput = z.input<typeof OacBlockSchema>;
  609. export type CanonicalAgent = z.infer<typeof CanonicalAgentSchema>;
  610. export type AgentMetadata = z.infer<typeof AgentMetadataSchema>;
  611. export type OpenAgent = z.infer<typeof OpenAgentSchema>;
  612. export type ToolConfig = z.infer<typeof ToolConfigSchema>;
  613. // ============================================================================
  614. // Interfaces
  615. // ============================================================================
  616. /**
  617. * Describes the capabilities of a specific tool/platform that OpenAgent
  618. * configurations can be converted to.
  619. */
  620. export interface ToolCapabilities {
  621. name: string;
  622. displayName: string;
  623. supportsMultipleAgents: boolean;
  624. supportsSkills: boolean;
  625. supportsHooks: boolean;
  626. supportsGranularPermissions: boolean;
  627. supportsContexts: boolean;
  628. supportsCustomModels: boolean;
  629. supportsTemperature: boolean;
  630. supportsMaxSteps: boolean;
  631. configFormat: "markdown" | "yaml" | "json" | "plain";
  632. outputStructure: "single-file" | "multi-file" | "directory";
  633. notes?: string[];
  634. }
  635. /**
  636. * Result of converting an OpenAgent configuration to another tool's format.
  637. * Includes the generated config files, warnings, and optional errors.
  638. */
  639. export interface ConversionResult {
  640. success: boolean;
  641. configs: ToolConfig[];
  642. warnings: string[];
  643. errors?: string[];
  644. capabilities?: ToolCapabilities;
  645. }