@oh-my-pi/pi-coding-agent 12.9.0 → 12.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,26 +1,50 @@
1
1
  import {
2
2
  type Api,
3
3
  type AssistantMessageEventStream,
4
+ anthropicModelManagerOptions,
4
5
  type Context,
6
+ cerebrasModelManagerOptions,
7
+ createModelManager,
8
+ cursorModelManagerOptions,
9
+ getBundledModels,
10
+ getBundledProviders,
5
11
  getGitHubCopilotBaseUrl,
6
- getModels,
7
- getProviders,
12
+ githubCopilotModelManagerOptions,
13
+ googleAntigravityModelManagerOptions,
14
+ googleGeminiCliModelManagerOptions,
15
+ googleModelManagerOptions,
16
+ groqModelManagerOptions,
17
+ kimiCodeModelManagerOptions,
8
18
  type Model,
19
+ type ModelManagerOptions,
20
+ mistralModelManagerOptions,
9
21
  normalizeDomain,
10
22
  type OAuthCredentials,
11
23
  type OAuthLoginCallbacks,
24
+ openaiCodexModelManagerOptions,
25
+ openaiModelManagerOptions,
26
+ opencodeModelManagerOptions,
27
+ openrouterModelManagerOptions,
12
28
  registerCustomApi,
13
29
  registerOAuthProvider,
14
30
  type SimpleStreamOptions,
31
+ syntheticModelManagerOptions,
15
32
  unregisterCustomApis,
16
33
  unregisterOAuthProviders,
34
+ vercelAiGatewayModelManagerOptions,
35
+ xaiModelManagerOptions,
17
36
  } from "@oh-my-pi/pi-ai";
18
37
  import { logger } from "@oh-my-pi/pi-utils";
19
38
  import { type Static, Type } from "@sinclair/typebox";
20
- import AjvModule from "ajv";
21
39
  import { type ConfigError, ConfigFile } from "../config";
22
40
  import type { ThemeColor } from "../modes/theme/theme";
23
- import type { AuthStorage } from "../session/auth-storage";
41
+ import type { AuthStorage, OAuthCredential } from "../session/auth-storage";
42
+
43
+ export const kNoAuth = "N/A";
44
+
45
+ export function isAuthenticated(apiKey: string | undefined | null): apiKey is string {
46
+ return Boolean(apiKey) && apiKey !== kNoAuth;
47
+ }
24
48
 
25
49
  export type ModelRole = "default" | "smol" | "slow" | "plan" | "commit";
26
50
 
@@ -40,8 +64,6 @@ export const MODEL_ROLES: Record<ModelRole, ModelRoleInfo> = {
40
64
 
41
65
  export const MODEL_ROLE_IDS: ModelRole[] = ["default", "smol", "slow", "plan", "commit"];
42
66
 
43
- const _Ajv = (AjvModule as any).default || AjvModule;
44
-
45
67
  const OpenRouterRoutingSchema = Type.Object({
46
68
  only: Type.Optional(Type.Array(Type.String())),
47
69
  order: Type.Optional(Type.Array(Type.String())),
@@ -252,19 +274,61 @@ function resolveApiKeyConfig(keyConfig: string): string | undefined {
252
274
  return keyConfig;
253
275
  }
254
276
 
277
+ function extractGoogleOAuthToken(value: string | undefined): string | undefined {
278
+ if (!isAuthenticated(value)) return undefined;
279
+ try {
280
+ const parsed = JSON.parse(value) as { token?: unknown };
281
+ if (Object.hasOwn(parsed, "token")) {
282
+ if (typeof parsed.token !== "string") {
283
+ return undefined;
284
+ }
285
+ const token = parsed.token.trim();
286
+ return token.length > 0 ? token : undefined;
287
+ }
288
+ } catch {
289
+ // OAuth values for Google providers are expected to be JSON, but custom setups may already provide raw token.
290
+ }
291
+ return value;
292
+ }
293
+
294
+ function getOAuthCredentialsForProvider(authStorage: AuthStorage, provider: string): OAuthCredential[] {
295
+ const providerEntry = authStorage.getAll()[provider];
296
+ if (!providerEntry) {
297
+ return [];
298
+ }
299
+ const entries = Array.isArray(providerEntry) ? providerEntry : [providerEntry];
300
+ return entries.filter((entry): entry is OAuthCredential => entry.type === "oauth");
301
+ }
302
+
303
+ function resolveOAuthAccountIdForAccessToken(
304
+ authStorage: AuthStorage,
305
+ provider: string,
306
+ accessToken: string,
307
+ ): string | undefined {
308
+ const oauthCredentials = getOAuthCredentialsForProvider(authStorage, provider);
309
+ const matchingCredential = oauthCredentials.find(credential => credential.access === accessToken);
310
+ if (matchingCredential) {
311
+ return matchingCredential.accountId;
312
+ }
313
+ if (oauthCredentials.length === 1) {
314
+ return oauthCredentials[0].accountId;
315
+ }
316
+ return undefined;
317
+ }
318
+
255
319
  function mergeCompat(
256
320
  baseCompat: Model<Api>["compat"],
257
321
  overrideCompat: ModelOverride["compat"],
258
322
  ): Model<Api>["compat"] | undefined {
259
323
  if (!overrideCompat) return baseCompat;
260
- const base = baseCompat as any;
261
- const override = overrideCompat as any;
262
- const merged = { ...base, ...override };
263
- if (base?.openRouterRouting || override.openRouterRouting) {
264
- merged.openRouterRouting = { ...base?.openRouterRouting, ...override.openRouterRouting };
324
+ const base = baseCompat ?? {};
325
+ const override = overrideCompat;
326
+ const merged: NonNullable<Model<Api>["compat"]> = { ...base, ...override };
327
+ if (baseCompat?.openRouterRouting || overrideCompat.openRouterRouting) {
328
+ merged.openRouterRouting = { ...baseCompat?.openRouterRouting, ...overrideCompat.openRouterRouting };
265
329
  }
266
- if (base?.vercelGatewayRouting || override.vercelGatewayRouting) {
267
- merged.vercelGatewayRouting = { ...base?.vercelGatewayRouting, ...override.vercelGatewayRouting };
330
+ if (baseCompat?.vercelGatewayRouting || overrideCompat.vercelGatewayRouting) {
331
+ merged.vercelGatewayRouting = { ...baseCompat?.vercelGatewayRouting, ...overrideCompat.vercelGatewayRouting };
268
332
  }
269
333
  return merged;
270
334
  }
@@ -384,8 +448,8 @@ export class ModelRegistry {
384
448
  overrides: Map<string, ProviderOverride>,
385
449
  modelOverrides: Map<string, Map<string, ModelOverride>>,
386
450
  ): Model<Api>[] {
387
- return getProviders().flatMap(provider => {
388
- const models = getModels(provider as any) as Model<Api>[];
451
+ return getBundledProviders().flatMap(provider => {
452
+ const models = getBundledModels(provider as Parameters<typeof getBundledModels>[0]) as Model<Api>[];
389
453
  const providerOverride = overrides.get(provider);
390
454
  const perModelOverrides = modelOverrides.get(provider);
391
455
 
@@ -516,11 +580,35 @@ export class ModelRegistry {
516
580
  }
517
581
 
518
582
  async #refreshRuntimeDiscoveries(): Promise<void> {
519
- if (this.#discoverableProviders.length === 0) return;
520
- const discovered = await Promise.all(
521
- this.#discoverableProviders.map(provider => this.#discoverProviderModels(provider)),
583
+ const configuredDiscoveriesPromise =
584
+ this.#discoverableProviders.length === 0
585
+ ? Promise.resolve<Model<Api>[]>([])
586
+ : Promise.all(this.#discoverableProviders.map(provider => this.#discoverProviderModels(provider))).then(
587
+ results => results.flat(),
588
+ );
589
+ const [configuredDiscovered, builtInDiscovered] = await Promise.all([
590
+ configuredDiscoveriesPromise,
591
+ this.#discoverBuiltInProviderModels(),
592
+ ]);
593
+ const discovered = [...configuredDiscovered, ...builtInDiscovered];
594
+ if (discovered.length === 0) {
595
+ return;
596
+ }
597
+ const merged = this.#mergeCustomModels(
598
+ this.#models,
599
+ discovered.map(model => {
600
+ const existing =
601
+ this.find(model.provider, model.id) ??
602
+ this.#models.find(candidate => candidate.provider === model.provider);
603
+ return existing
604
+ ? {
605
+ ...model,
606
+ baseUrl: existing.baseUrl,
607
+ headers: existing.headers ? { ...existing.headers, ...model.headers } : model.headers,
608
+ }
609
+ : model;
610
+ }),
522
611
  );
523
- const merged = this.#mergeCustomModels(this.#models, discovered.flat());
524
612
  this.#models = this.#applyModelOverrides(merged, this.#modelOverrides);
525
613
  }
526
614
 
@@ -531,6 +619,208 @@ export class ModelRegistry {
531
619
  }
532
620
  }
533
621
 
622
+ async #discoverBuiltInProviderModels(): Promise<Model<Api>[]> {
623
+ const managerOptions = await this.#collectBuiltInModelManagerOptions();
624
+ if (managerOptions.length === 0) {
625
+ return [];
626
+ }
627
+ const discoveries = await Promise.all(managerOptions.map(options => this.#discoverWithModelManager(options)));
628
+ return discoveries.flat();
629
+ }
630
+
631
+ async #collectBuiltInModelManagerOptions(): Promise<ModelManagerOptions<Api>[]> {
632
+ const [
633
+ anthropicApiKey,
634
+ openaiApiKey,
635
+ groqApiKey,
636
+ cerebrasApiKey,
637
+ xaiApiKey,
638
+ mistralApiKey,
639
+ opencodeApiKey,
640
+ openrouterApiKey,
641
+ vercelGatewayApiKey,
642
+ kimiApiKey,
643
+ syntheticApiKey,
644
+ githubCopilotApiKey,
645
+ googleApiKey,
646
+ cursorApiKey,
647
+ googleAntigravityApiKey,
648
+ googleGeminiCliApiKey,
649
+ codexAccessToken,
650
+ ] = await Promise.all([
651
+ this.getApiKeyForProvider("anthropic"),
652
+ this.getApiKeyForProvider("openai"),
653
+ this.getApiKeyForProvider("groq"),
654
+ this.getApiKeyForProvider("cerebras"),
655
+ this.getApiKeyForProvider("xai"),
656
+ this.getApiKeyForProvider("mistral"),
657
+ this.getApiKeyForProvider("opencode"),
658
+ this.getApiKeyForProvider("openrouter"),
659
+ this.getApiKeyForProvider("vercel-ai-gateway"),
660
+ this.getApiKeyForProvider("kimi-code"),
661
+ this.getApiKeyForProvider("synthetic"),
662
+ this.getApiKeyForProvider("github-copilot"),
663
+ this.getApiKeyForProvider("google"),
664
+ this.getApiKeyForProvider("cursor"),
665
+ this.getApiKeyForProvider("google-antigravity"),
666
+ this.getApiKeyForProvider("google-gemini-cli"),
667
+ this.getApiKeyForProvider("openai-codex"),
668
+ ]);
669
+
670
+ const options: ModelManagerOptions<Api>[] = [];
671
+ if (isAuthenticated(anthropicApiKey)) {
672
+ options.push(
673
+ anthropicModelManagerOptions({
674
+ apiKey: anthropicApiKey,
675
+ baseUrl: this.getProviderBaseUrl("anthropic"),
676
+ }),
677
+ );
678
+ }
679
+ if (isAuthenticated(openaiApiKey)) {
680
+ options.push(
681
+ openaiModelManagerOptions({
682
+ apiKey: openaiApiKey,
683
+ baseUrl: this.getProviderBaseUrl("openai"),
684
+ }),
685
+ );
686
+ }
687
+ if (isAuthenticated(groqApiKey)) {
688
+ options.push(
689
+ groqModelManagerOptions({
690
+ apiKey: groqApiKey,
691
+ baseUrl: this.getProviderBaseUrl("groq"),
692
+ }),
693
+ );
694
+ }
695
+ if (isAuthenticated(cerebrasApiKey)) {
696
+ options.push(
697
+ cerebrasModelManagerOptions({
698
+ apiKey: cerebrasApiKey,
699
+ baseUrl: this.getProviderBaseUrl("cerebras"),
700
+ }),
701
+ );
702
+ }
703
+ if (isAuthenticated(xaiApiKey)) {
704
+ options.push(
705
+ xaiModelManagerOptions({
706
+ apiKey: xaiApiKey,
707
+ baseUrl: this.getProviderBaseUrl("xai"),
708
+ }),
709
+ );
710
+ }
711
+ if (isAuthenticated(mistralApiKey)) {
712
+ options.push(
713
+ mistralModelManagerOptions({
714
+ apiKey: mistralApiKey,
715
+ baseUrl: this.getProviderBaseUrl("mistral"),
716
+ }),
717
+ );
718
+ }
719
+ if (isAuthenticated(opencodeApiKey)) {
720
+ options.push(
721
+ opencodeModelManagerOptions({
722
+ apiKey: opencodeApiKey,
723
+ baseUrl: this.getProviderBaseUrl("opencode"),
724
+ }),
725
+ );
726
+ }
727
+ if (isAuthenticated(openrouterApiKey)) {
728
+ options.push(
729
+ openrouterModelManagerOptions({
730
+ apiKey: openrouterApiKey,
731
+ baseUrl: this.getProviderBaseUrl("openrouter"),
732
+ }),
733
+ );
734
+ }
735
+ if (isAuthenticated(vercelGatewayApiKey)) {
736
+ options.push(
737
+ vercelAiGatewayModelManagerOptions({
738
+ apiKey: vercelGatewayApiKey,
739
+ baseUrl: this.getProviderBaseUrl("vercel-ai-gateway"),
740
+ }),
741
+ );
742
+ }
743
+ if (isAuthenticated(kimiApiKey)) {
744
+ options.push(
745
+ kimiCodeModelManagerOptions({
746
+ apiKey: kimiApiKey,
747
+ baseUrl: this.getProviderBaseUrl("kimi-code"),
748
+ }),
749
+ );
750
+ }
751
+ if (isAuthenticated(syntheticApiKey)) {
752
+ options.push(
753
+ syntheticModelManagerOptions({
754
+ apiKey: syntheticApiKey,
755
+ baseUrl: this.getProviderBaseUrl("synthetic"),
756
+ }),
757
+ );
758
+ }
759
+ if (isAuthenticated(githubCopilotApiKey)) {
760
+ options.push(
761
+ githubCopilotModelManagerOptions({
762
+ apiKey: githubCopilotApiKey,
763
+ baseUrl: this.getProviderBaseUrl("github-copilot"),
764
+ }),
765
+ );
766
+ }
767
+ if (isAuthenticated(googleApiKey)) options.push(googleModelManagerOptions({ apiKey: googleApiKey }));
768
+ if (isAuthenticated(cursorApiKey)) {
769
+ options.push(
770
+ cursorModelManagerOptions({
771
+ apiKey: cursorApiKey,
772
+ baseUrl: this.getProviderBaseUrl("cursor"),
773
+ }),
774
+ );
775
+ }
776
+
777
+ const antigravityToken = extractGoogleOAuthToken(googleAntigravityApiKey);
778
+ if (isAuthenticated(antigravityToken)) {
779
+ options.push(
780
+ googleAntigravityModelManagerOptions({
781
+ oauthToken: antigravityToken,
782
+ endpoint: this.getProviderBaseUrl("google-antigravity"),
783
+ }),
784
+ );
785
+ }
786
+
787
+ const geminiCliToken = extractGoogleOAuthToken(googleGeminiCliApiKey);
788
+ if (isAuthenticated(geminiCliToken)) {
789
+ options.push(
790
+ googleGeminiCliModelManagerOptions({
791
+ oauthToken: geminiCliToken,
792
+ endpoint: this.getProviderBaseUrl("google-gemini-cli"),
793
+ }),
794
+ );
795
+ }
796
+
797
+ if (isAuthenticated(codexAccessToken)) {
798
+ const codexAccountId = resolveOAuthAccountIdForAccessToken(this.authStorage, "openai-codex", codexAccessToken);
799
+ options.push(
800
+ openaiCodexModelManagerOptions({
801
+ accessToken: codexAccessToken,
802
+ accountId: codexAccountId,
803
+ }),
804
+ );
805
+ }
806
+
807
+ return options;
808
+ }
809
+
810
+ async #discoverWithModelManager(options: ModelManagerOptions<Api>): Promise<Model<Api>[]> {
811
+ try {
812
+ const manager = createModelManager(options);
813
+ const result = await manager.refresh("online");
814
+ return result.models;
815
+ } catch (error) {
816
+ logger.warn("model discovery failed for provider", {
817
+ provider: options.providerId,
818
+ error: error instanceof Error ? error.message : String(error),
819
+ });
820
+ return [];
821
+ }
822
+ }
823
+
534
824
  async #discoverOllamaModels(providerConfig: DiscoveryProviderConfig): Promise<Model<Api>[]> {
535
825
  const endpoint = this.#normalizeOllamaBaseUrl(providerConfig.baseUrl);
536
826
  const tagsUrl = `${endpoint}/api/tags`;
@@ -698,7 +988,7 @@ export class ModelRegistry {
698
988
  */
699
989
  async getApiKey(model: Model<Api>, sessionId?: string): Promise<string | undefined> {
700
990
  if (this.#keylessProviders.has(model.provider)) {
701
- return "<no-auth>";
991
+ return kNoAuth;
702
992
  }
703
993
  return this.authStorage.getApiKey(model.provider, sessionId, { baseUrl: model.baseUrl });
704
994
  }
@@ -708,7 +998,7 @@ export class ModelRegistry {
708
998
  */
709
999
  async getApiKeyForProvider(provider: string, sessionId?: string, baseUrl?: string): Promise<string | undefined> {
710
1000
  if (this.#keylessProviders.has(provider)) {
711
- return "<no-auth>";
1001
+ return kNoAuth;
712
1002
  }
713
1003
  return this.authStorage.getApiKey(provider, sessionId, { baseUrl });
714
1004
  }
@@ -12,7 +12,7 @@ import type { Settings } from "./settings";
12
12
  /** Default model IDs for each known provider */
13
13
  export const defaultModelPerProvider: Record<KnownProvider, string> = {
14
14
  "amazon-bedrock": "us.anthropic.claude-opus-4-6-v1",
15
- anthropic: "claude-opus-4-6",
15
+ anthropic: "claude-sonnet-4-6",
16
16
  openai: "gpt-5.1-codex",
17
17
  "openai-codex": "gpt-5.3-codex",
18
18
  google: "gemini-2.5-pro",
@@ -20,9 +20,9 @@ export const defaultModelPerProvider: Record<KnownProvider, string> = {
20
20
  "google-antigravity": "gemini-3-pro-high",
21
21
  "google-vertex": "gemini-3-pro-preview",
22
22
  "github-copilot": "gpt-4o",
23
- cursor: "claude-opus-4-6",
23
+ cursor: "claude-sonnet-4-6",
24
24
  openrouter: "openai/gpt-5.1-codex",
25
- "vercel-ai-gateway": "anthropic/claude-opus-4-6",
25
+ "vercel-ai-gateway": "anthropic/claude-sonnet-4-6",
26
26
  xai: "grok-4-fast-non-reasoning",
27
27
  groq: "openai/gpt-oss-120b",
28
28
  cerebras: "zai-glm-4.6",
@@ -31,8 +31,9 @@ export const defaultModelPerProvider: Record<KnownProvider, string> = {
31
31
  minimax: "MiniMax-M2.5",
32
32
  "minimax-code": "MiniMax-M2.5",
33
33
  "minimax-code-cn": "MiniMax-M2.5",
34
- opencode: "claude-opus-4-6",
34
+ opencode: "claude-sonnet-4-6",
35
35
  "kimi-code": "kimi-k2.5",
36
+ synthetic: "hf:moonshotai/Kimi-K2.5",
36
37
  };
37
38
 
38
39
  export interface ScopedModel {
@@ -889,6 +889,17 @@ export const SETTINGS_SCHEMA = {
889
889
  default: "discard",
890
890
  ui: { tab: "ttsr", label: "TTSR context mode", description: "What to do with partial output when TTSR triggers" },
891
891
  },
892
+ "ttsr.interruptMode": {
893
+ type: "enum",
894
+ values: ["never", "prose-only", "tool-only", "always"] as const,
895
+ default: "always",
896
+ ui: {
897
+ tab: "ttsr",
898
+ label: "TTSR interrupt mode",
899
+ description: "When to interrupt mid-stream vs inject warning after completion",
900
+ submenu: true,
901
+ },
902
+ },
892
903
  "ttsr.repeatMode": {
893
904
  type: "enum",
894
905
  values: ["once", "after-gap"] as const,
@@ -1107,6 +1118,7 @@ export interface CommitSettings {
1107
1118
  export interface TtsrSettings {
1108
1119
  enabled: boolean;
1109
1120
  contextMode: "discard" | "keep";
1121
+ interruptMode: "never" | "prose-only" | "tool-only" | "always";
1110
1122
  repeatMode: "once" | "after-gap";
1111
1123
  repeatGap: number;
1112
1124
  }
@@ -1,36 +1,199 @@
1
1
  /**
2
2
  * Agents (standard) Provider
3
3
  *
4
- * Loads user-level skills from ~/.agents/skills.
4
+ * Loads user-level skills, rules, prompts, commands, context files, and system prompts from ~/.agent/.
5
5
  */
6
6
  import * as path from "node:path";
7
7
  import { registerProvider } from "../capability";
8
+ import { type ContextFile, contextFileCapability } from "../capability/context-file";
9
+ import { readFile } from "../capability/fs";
10
+ import { type Prompt, promptCapability } from "../capability/prompt";
11
+ import { type Rule, ruleCapability } from "../capability/rule";
8
12
  import { type Skill, skillCapability } from "../capability/skill";
13
+ import { type SlashCommand, slashCommandCapability } from "../capability/slash-command";
14
+ import { type SystemPrompt, systemPromptCapability } from "../capability/system-prompt";
9
15
  import type { LoadContext, LoadResult } from "../capability/types";
10
- import { loadSkillsFromDir } from "./helpers";
16
+ import { buildRuleFromMarkdown, createSourceMeta, loadFilesFromDir, loadSkillsFromDir } from "./helpers";
11
17
 
12
18
  const PROVIDER_ID = "agents";
13
19
  const DISPLAY_NAME = "Agents (standard)";
14
20
  const PRIORITY = 70;
21
+ const USER_AGENT_DIR_CANDIDATES = [".agent", ".agents"] as const;
15
22
 
16
- async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
17
- const userSkillsDir = path.join(ctx.home, ".agents", "skills");
18
- const result = await loadSkillsFromDir(ctx, {
19
- dir: userSkillsDir,
20
- providerId: PROVIDER_ID,
21
- level: "user",
22
- });
23
+ function getUserAgentPathCandidates(ctx: LoadContext, ...segments: string[]): string[] {
24
+ return USER_AGENT_DIR_CANDIDATES.map(baseDir => path.join(ctx.home, baseDir, ...segments));
25
+ }
23
26
 
27
+ async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
28
+ const items: Skill[] = [];
29
+ const warnings: string[] = [];
30
+ for (const userSkillsDir of getUserAgentPathCandidates(ctx, "skills")) {
31
+ const result = await loadSkillsFromDir(ctx, {
32
+ dir: userSkillsDir,
33
+ providerId: PROVIDER_ID,
34
+ level: "user",
35
+ });
36
+ items.push(...result.items);
37
+ warnings.push(...(result.warnings ?? []));
38
+ }
24
39
  return {
25
- items: result.items,
26
- warnings: result.warnings ?? [],
40
+ items,
41
+ warnings,
27
42
  };
28
43
  }
29
44
 
30
45
  registerProvider<Skill>(skillCapability.id, {
31
46
  id: PROVIDER_ID,
32
47
  displayName: DISPLAY_NAME,
33
- description: "Load skills from ~/.agents/skills",
48
+ description: "Load skills from ~/.agent/skills (fallback ~/.agents/skills)",
34
49
  priority: PRIORITY,
35
50
  load: loadSkills,
36
51
  });
52
+
53
+ // Rules
54
+ async function loadRules(ctx: LoadContext): Promise<LoadResult<Rule>> {
55
+ const items: Rule[] = [];
56
+ const warnings: string[] = [];
57
+ for (const userRulesDir of getUserAgentPathCandidates(ctx, "rules")) {
58
+ const result = await loadFilesFromDir<Rule>(ctx, userRulesDir, PROVIDER_ID, "user", {
59
+ extensions: ["md", "mdc"],
60
+ transform: (name, content, filePath, source) =>
61
+ buildRuleFromMarkdown(name, content, filePath, source, { stripNamePattern: /\.(md|mdc)$/ }),
62
+ });
63
+ items.push(...result.items);
64
+ warnings.push(...(result.warnings ?? []));
65
+ }
66
+ return {
67
+ items,
68
+ warnings,
69
+ };
70
+ }
71
+
72
+ registerProvider<Rule>(ruleCapability.id, {
73
+ id: PROVIDER_ID,
74
+ displayName: DISPLAY_NAME,
75
+ description: "Load rules from ~/.agent/rules (fallback ~/.agents/rules)",
76
+ priority: PRIORITY,
77
+ load: loadRules,
78
+ });
79
+
80
+ // Prompts
81
+ async function loadPrompts(ctx: LoadContext): Promise<LoadResult<Prompt>> {
82
+ const items: Prompt[] = [];
83
+ const warnings: string[] = [];
84
+ for (const userPromptsDir of getUserAgentPathCandidates(ctx, "prompts")) {
85
+ const result = await loadFilesFromDir<Prompt>(ctx, userPromptsDir, PROVIDER_ID, "user", {
86
+ extensions: ["md"],
87
+ transform: (name, content, filePath, source) => ({
88
+ name: name.replace(/\.md$/, ""),
89
+ path: filePath,
90
+ content,
91
+ _source: source,
92
+ }),
93
+ });
94
+ items.push(...result.items);
95
+ warnings.push(...(result.warnings ?? []));
96
+ }
97
+ return {
98
+ items,
99
+ warnings,
100
+ };
101
+ }
102
+
103
+ registerProvider<Prompt>(promptCapability.id, {
104
+ id: PROVIDER_ID,
105
+ displayName: DISPLAY_NAME,
106
+ description: "Load prompts from ~/.agent/prompts (fallback ~/.agents/prompts)",
107
+ priority: PRIORITY,
108
+ load: loadPrompts,
109
+ });
110
+
111
+ // Slash Commands
112
+ async function loadSlashCommands(ctx: LoadContext): Promise<LoadResult<SlashCommand>> {
113
+ const items: SlashCommand[] = [];
114
+ const warnings: string[] = [];
115
+ for (const userCommandsDir of getUserAgentPathCandidates(ctx, "commands")) {
116
+ const result = await loadFilesFromDir<SlashCommand>(ctx, userCommandsDir, PROVIDER_ID, "user", {
117
+ extensions: ["md"],
118
+ transform: (name, content, filePath, source) => ({
119
+ name: name.replace(/\.md$/, ""),
120
+ path: filePath,
121
+ content,
122
+ level: "user",
123
+ _source: source,
124
+ }),
125
+ });
126
+ items.push(...result.items);
127
+ warnings.push(...(result.warnings ?? []));
128
+ }
129
+ return {
130
+ items,
131
+ warnings,
132
+ };
133
+ }
134
+
135
+ registerProvider<SlashCommand>(slashCommandCapability.id, {
136
+ id: PROVIDER_ID,
137
+ displayName: DISPLAY_NAME,
138
+ description: "Load commands from ~/.agent/commands (fallback ~/.agents/commands)",
139
+ priority: PRIORITY,
140
+ load: loadSlashCommands,
141
+ });
142
+
143
+ // Context Files (AGENTS.md)
144
+ async function loadContextFiles(ctx: LoadContext): Promise<LoadResult<ContextFile>> {
145
+ const items: ContextFile[] = [];
146
+ for (const agentsPath of getUserAgentPathCandidates(ctx, "AGENTS.md")) {
147
+ const content = await readFile(agentsPath);
148
+ if (!content) {
149
+ continue;
150
+ }
151
+ items.push({
152
+ path: agentsPath,
153
+ content,
154
+ level: "user",
155
+ _source: createSourceMeta(PROVIDER_ID, agentsPath, "user"),
156
+ });
157
+ }
158
+ return {
159
+ items,
160
+ warnings: [],
161
+ };
162
+ }
163
+
164
+ registerProvider<ContextFile>(contextFileCapability.id, {
165
+ id: PROVIDER_ID,
166
+ displayName: DISPLAY_NAME,
167
+ description: "Load AGENTS.md from ~/.agent (fallback ~/.agents)",
168
+ priority: PRIORITY,
169
+ load: loadContextFiles,
170
+ });
171
+
172
+ // System Prompt (SYSTEM.md)
173
+ async function loadSystemPrompt(ctx: LoadContext): Promise<LoadResult<SystemPrompt>> {
174
+ const items: SystemPrompt[] = [];
175
+ for (const systemPath of getUserAgentPathCandidates(ctx, "SYSTEM.md")) {
176
+ const content = await readFile(systemPath);
177
+ if (!content) {
178
+ continue;
179
+ }
180
+ items.push({
181
+ path: systemPath,
182
+ content,
183
+ level: "user",
184
+ _source: createSourceMeta(PROVIDER_ID, systemPath, "user"),
185
+ });
186
+ }
187
+ return {
188
+ items,
189
+ warnings: [],
190
+ };
191
+ }
192
+
193
+ registerProvider<SystemPrompt>(systemPromptCapability.id, {
194
+ id: PROVIDER_ID,
195
+ displayName: DISPLAY_NAME,
196
+ description: "Load SYSTEM.md from ~/.agent (fallback ~/.agents)",
197
+ priority: PRIORITY,
198
+ load: loadSystemPrompt,
199
+ });