@oh-my-pi/pi-coding-agent 12.8.2 → 12.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -0
- package/package.json +7 -7
- package/src/capability/rule.ts +173 -5
- package/src/cli/args.ts +3 -0
- package/src/cli/update-cli.ts +1 -66
- package/src/commands/launch.ts +3 -0
- package/src/config/model-registry.ts +300 -21
- package/src/config/model-resolver.ts +4 -4
- package/src/config/settings-schema.ts +12 -0
- package/src/discovery/agents.ts +175 -12
- package/src/discovery/builtin.ts +3 -13
- package/src/discovery/cline.ts +4 -45
- package/src/discovery/cursor.ts +2 -29
- package/src/discovery/helpers.ts +43 -0
- package/src/discovery/index.ts +1 -0
- package/src/discovery/opencode.ts +394 -0
- package/src/discovery/windsurf.ts +5 -44
- package/src/export/ttsr.ts +324 -54
- package/src/extensibility/custom-tools/wrapper.ts +1 -11
- package/src/internal-urls/index.ts +4 -2
- package/src/internal-urls/memory-protocol.ts +133 -0
- package/src/internal-urls/router.ts +4 -2
- package/src/internal-urls/skill-protocol.ts +1 -1
- package/src/internal-urls/types.ts +6 -2
- package/src/main.ts +5 -0
- package/src/memories/index.ts +6 -13
- package/src/modes/components/settings-defs.ts +6 -0
- package/src/modes/components/status-line/segments.ts +3 -2
- package/src/modes/rpc/rpc-client.ts +16 -0
- package/src/prompts/memories/consolidation.md +1 -1
- package/src/prompts/memories/read_path.md +4 -4
- package/src/prompts/memories/stage_one_input.md +1 -2
- package/src/prompts/tools/bash.md +10 -23
- package/src/prompts/tools/read.md +2 -0
- package/src/sdk.ts +25 -10
- package/src/session/agent-session.ts +252 -44
- package/src/session/session-manager.ts +79 -36
- package/src/tools/bash-skill-urls.ts +177 -0
- package/src/tools/bash.ts +7 -1
- package/src/tools/fetch.ts +6 -2
- package/src/tools/index.ts +2 -2
- package/src/tools/output-meta.ts +49 -42
- package/src/tools/read.ts +2 -2
|
@@ -1,26 +1,49 @@
|
|
|
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
|
-
|
|
7
|
-
|
|
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,
|
|
15
31
|
unregisterCustomApis,
|
|
16
32
|
unregisterOAuthProviders,
|
|
33
|
+
vercelAiGatewayModelManagerOptions,
|
|
34
|
+
xaiModelManagerOptions,
|
|
17
35
|
} from "@oh-my-pi/pi-ai";
|
|
18
36
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
19
37
|
import { type Static, Type } from "@sinclair/typebox";
|
|
20
|
-
import AjvModule from "ajv";
|
|
21
38
|
import { type ConfigError, ConfigFile } from "../config";
|
|
22
39
|
import type { ThemeColor } from "../modes/theme/theme";
|
|
23
|
-
import type { AuthStorage } from "../session/auth-storage";
|
|
40
|
+
import type { AuthStorage, OAuthCredential } from "../session/auth-storage";
|
|
41
|
+
|
|
42
|
+
export const kNoAuth = "N/A";
|
|
43
|
+
|
|
44
|
+
export function isAuthenticated(apiKey: string | undefined | null): apiKey is string {
|
|
45
|
+
return Boolean(apiKey) && apiKey !== kNoAuth;
|
|
46
|
+
}
|
|
24
47
|
|
|
25
48
|
export type ModelRole = "default" | "smol" | "slow" | "plan" | "commit";
|
|
26
49
|
|
|
@@ -40,8 +63,6 @@ export const MODEL_ROLES: Record<ModelRole, ModelRoleInfo> = {
|
|
|
40
63
|
|
|
41
64
|
export const MODEL_ROLE_IDS: ModelRole[] = ["default", "smol", "slow", "plan", "commit"];
|
|
42
65
|
|
|
43
|
-
const _Ajv = (AjvModule as any).default || AjvModule;
|
|
44
|
-
|
|
45
66
|
const OpenRouterRoutingSchema = Type.Object({
|
|
46
67
|
only: Type.Optional(Type.Array(Type.String())),
|
|
47
68
|
order: Type.Optional(Type.Array(Type.String())),
|
|
@@ -252,19 +273,61 @@ function resolveApiKeyConfig(keyConfig: string): string | undefined {
|
|
|
252
273
|
return keyConfig;
|
|
253
274
|
}
|
|
254
275
|
|
|
276
|
+
function extractGoogleOAuthToken(value: string | undefined): string | undefined {
|
|
277
|
+
if (!isAuthenticated(value)) return undefined;
|
|
278
|
+
try {
|
|
279
|
+
const parsed = JSON.parse(value) as { token?: unknown };
|
|
280
|
+
if (Object.hasOwn(parsed, "token")) {
|
|
281
|
+
if (typeof parsed.token !== "string") {
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
284
|
+
const token = parsed.token.trim();
|
|
285
|
+
return token.length > 0 ? token : undefined;
|
|
286
|
+
}
|
|
287
|
+
} catch {
|
|
288
|
+
// OAuth values for Google providers are expected to be JSON, but custom setups may already provide raw token.
|
|
289
|
+
}
|
|
290
|
+
return value;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function getOAuthCredentialsForProvider(authStorage: AuthStorage, provider: string): OAuthCredential[] {
|
|
294
|
+
const providerEntry = authStorage.getAll()[provider];
|
|
295
|
+
if (!providerEntry) {
|
|
296
|
+
return [];
|
|
297
|
+
}
|
|
298
|
+
const entries = Array.isArray(providerEntry) ? providerEntry : [providerEntry];
|
|
299
|
+
return entries.filter((entry): entry is OAuthCredential => entry.type === "oauth");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function resolveOAuthAccountIdForAccessToken(
|
|
303
|
+
authStorage: AuthStorage,
|
|
304
|
+
provider: string,
|
|
305
|
+
accessToken: string,
|
|
306
|
+
): string | undefined {
|
|
307
|
+
const oauthCredentials = getOAuthCredentialsForProvider(authStorage, provider);
|
|
308
|
+
const matchingCredential = oauthCredentials.find(credential => credential.access === accessToken);
|
|
309
|
+
if (matchingCredential) {
|
|
310
|
+
return matchingCredential.accountId;
|
|
311
|
+
}
|
|
312
|
+
if (oauthCredentials.length === 1) {
|
|
313
|
+
return oauthCredentials[0].accountId;
|
|
314
|
+
}
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
|
|
255
318
|
function mergeCompat(
|
|
256
319
|
baseCompat: Model<Api>["compat"],
|
|
257
320
|
overrideCompat: ModelOverride["compat"],
|
|
258
321
|
): Model<Api>["compat"] | undefined {
|
|
259
322
|
if (!overrideCompat) return baseCompat;
|
|
260
|
-
const base = baseCompat
|
|
261
|
-
const override = overrideCompat
|
|
262
|
-
const merged = { ...base, ...override };
|
|
263
|
-
if (
|
|
264
|
-
merged.openRouterRouting = { ...
|
|
323
|
+
const base = baseCompat ?? {};
|
|
324
|
+
const override = overrideCompat;
|
|
325
|
+
const merged: NonNullable<Model<Api>["compat"]> = { ...base, ...override };
|
|
326
|
+
if (baseCompat?.openRouterRouting || overrideCompat.openRouterRouting) {
|
|
327
|
+
merged.openRouterRouting = { ...baseCompat?.openRouterRouting, ...overrideCompat.openRouterRouting };
|
|
265
328
|
}
|
|
266
|
-
if (
|
|
267
|
-
merged.vercelGatewayRouting = { ...
|
|
329
|
+
if (baseCompat?.vercelGatewayRouting || overrideCompat.vercelGatewayRouting) {
|
|
330
|
+
merged.vercelGatewayRouting = { ...baseCompat?.vercelGatewayRouting, ...overrideCompat.vercelGatewayRouting };
|
|
268
331
|
}
|
|
269
332
|
return merged;
|
|
270
333
|
}
|
|
@@ -384,8 +447,8 @@ export class ModelRegistry {
|
|
|
384
447
|
overrides: Map<string, ProviderOverride>,
|
|
385
448
|
modelOverrides: Map<string, Map<string, ModelOverride>>,
|
|
386
449
|
): Model<Api>[] {
|
|
387
|
-
return
|
|
388
|
-
const models =
|
|
450
|
+
return getBundledProviders().flatMap(provider => {
|
|
451
|
+
const models = getBundledModels(provider as Parameters<typeof getBundledModels>[0]) as Model<Api>[];
|
|
389
452
|
const providerOverride = overrides.get(provider);
|
|
390
453
|
const perModelOverrides = modelOverrides.get(provider);
|
|
391
454
|
|
|
@@ -516,11 +579,35 @@ export class ModelRegistry {
|
|
|
516
579
|
}
|
|
517
580
|
|
|
518
581
|
async #refreshRuntimeDiscoveries(): Promise<void> {
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
582
|
+
const configuredDiscoveriesPromise =
|
|
583
|
+
this.#discoverableProviders.length === 0
|
|
584
|
+
? Promise.resolve<Model<Api>[]>([])
|
|
585
|
+
: Promise.all(this.#discoverableProviders.map(provider => this.#discoverProviderModels(provider))).then(
|
|
586
|
+
results => results.flat(),
|
|
587
|
+
);
|
|
588
|
+
const [configuredDiscovered, builtInDiscovered] = await Promise.all([
|
|
589
|
+
configuredDiscoveriesPromise,
|
|
590
|
+
this.#discoverBuiltInProviderModels(),
|
|
591
|
+
]);
|
|
592
|
+
const discovered = [...configuredDiscovered, ...builtInDiscovered];
|
|
593
|
+
if (discovered.length === 0) {
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
const merged = this.#mergeCustomModels(
|
|
597
|
+
this.#models,
|
|
598
|
+
discovered.map(model => {
|
|
599
|
+
const existing =
|
|
600
|
+
this.find(model.provider, model.id) ??
|
|
601
|
+
this.#models.find(candidate => candidate.provider === model.provider);
|
|
602
|
+
return existing
|
|
603
|
+
? {
|
|
604
|
+
...model,
|
|
605
|
+
baseUrl: existing.baseUrl,
|
|
606
|
+
headers: existing.headers ? { ...existing.headers, ...model.headers } : model.headers,
|
|
607
|
+
}
|
|
608
|
+
: model;
|
|
609
|
+
}),
|
|
522
610
|
);
|
|
523
|
-
const merged = this.#mergeCustomModels(this.#models, discovered.flat());
|
|
524
611
|
this.#models = this.#applyModelOverrides(merged, this.#modelOverrides);
|
|
525
612
|
}
|
|
526
613
|
|
|
@@ -531,6 +618,198 @@ export class ModelRegistry {
|
|
|
531
618
|
}
|
|
532
619
|
}
|
|
533
620
|
|
|
621
|
+
async #discoverBuiltInProviderModels(): Promise<Model<Api>[]> {
|
|
622
|
+
const managerOptions = await this.#collectBuiltInModelManagerOptions();
|
|
623
|
+
if (managerOptions.length === 0) {
|
|
624
|
+
return [];
|
|
625
|
+
}
|
|
626
|
+
const discoveries = await Promise.all(managerOptions.map(options => this.#discoverWithModelManager(options)));
|
|
627
|
+
return discoveries.flat();
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
async #collectBuiltInModelManagerOptions(): Promise<ModelManagerOptions<Api>[]> {
|
|
631
|
+
const [
|
|
632
|
+
anthropicApiKey,
|
|
633
|
+
openaiApiKey,
|
|
634
|
+
groqApiKey,
|
|
635
|
+
cerebrasApiKey,
|
|
636
|
+
xaiApiKey,
|
|
637
|
+
mistralApiKey,
|
|
638
|
+
opencodeApiKey,
|
|
639
|
+
openrouterApiKey,
|
|
640
|
+
vercelGatewayApiKey,
|
|
641
|
+
kimiApiKey,
|
|
642
|
+
githubCopilotApiKey,
|
|
643
|
+
googleApiKey,
|
|
644
|
+
cursorApiKey,
|
|
645
|
+
googleAntigravityApiKey,
|
|
646
|
+
googleGeminiCliApiKey,
|
|
647
|
+
codexAccessToken,
|
|
648
|
+
] = await Promise.all([
|
|
649
|
+
this.getApiKeyForProvider("anthropic"),
|
|
650
|
+
this.getApiKeyForProvider("openai"),
|
|
651
|
+
this.getApiKeyForProvider("groq"),
|
|
652
|
+
this.getApiKeyForProvider("cerebras"),
|
|
653
|
+
this.getApiKeyForProvider("xai"),
|
|
654
|
+
this.getApiKeyForProvider("mistral"),
|
|
655
|
+
this.getApiKeyForProvider("opencode"),
|
|
656
|
+
this.getApiKeyForProvider("openrouter"),
|
|
657
|
+
this.getApiKeyForProvider("vercel-ai-gateway"),
|
|
658
|
+
this.getApiKeyForProvider("kimi-code"),
|
|
659
|
+
this.getApiKeyForProvider("github-copilot"),
|
|
660
|
+
this.getApiKeyForProvider("google"),
|
|
661
|
+
this.getApiKeyForProvider("cursor"),
|
|
662
|
+
this.getApiKeyForProvider("google-antigravity"),
|
|
663
|
+
this.getApiKeyForProvider("google-gemini-cli"),
|
|
664
|
+
this.getApiKeyForProvider("openai-codex"),
|
|
665
|
+
]);
|
|
666
|
+
|
|
667
|
+
const options: ModelManagerOptions<Api>[] = [];
|
|
668
|
+
if (isAuthenticated(anthropicApiKey)) {
|
|
669
|
+
options.push(
|
|
670
|
+
anthropicModelManagerOptions({
|
|
671
|
+
apiKey: anthropicApiKey,
|
|
672
|
+
baseUrl: this.getProviderBaseUrl("anthropic"),
|
|
673
|
+
}),
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
if (isAuthenticated(openaiApiKey)) {
|
|
677
|
+
options.push(
|
|
678
|
+
openaiModelManagerOptions({
|
|
679
|
+
apiKey: openaiApiKey,
|
|
680
|
+
baseUrl: this.getProviderBaseUrl("openai"),
|
|
681
|
+
}),
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
if (isAuthenticated(groqApiKey)) {
|
|
685
|
+
options.push(
|
|
686
|
+
groqModelManagerOptions({
|
|
687
|
+
apiKey: groqApiKey,
|
|
688
|
+
baseUrl: this.getProviderBaseUrl("groq"),
|
|
689
|
+
}),
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
if (isAuthenticated(cerebrasApiKey)) {
|
|
693
|
+
options.push(
|
|
694
|
+
cerebrasModelManagerOptions({
|
|
695
|
+
apiKey: cerebrasApiKey,
|
|
696
|
+
baseUrl: this.getProviderBaseUrl("cerebras"),
|
|
697
|
+
}),
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
if (isAuthenticated(xaiApiKey)) {
|
|
701
|
+
options.push(
|
|
702
|
+
xaiModelManagerOptions({
|
|
703
|
+
apiKey: xaiApiKey,
|
|
704
|
+
baseUrl: this.getProviderBaseUrl("xai"),
|
|
705
|
+
}),
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
if (isAuthenticated(mistralApiKey)) {
|
|
709
|
+
options.push(
|
|
710
|
+
mistralModelManagerOptions({
|
|
711
|
+
apiKey: mistralApiKey,
|
|
712
|
+
baseUrl: this.getProviderBaseUrl("mistral"),
|
|
713
|
+
}),
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
if (isAuthenticated(opencodeApiKey)) {
|
|
717
|
+
options.push(
|
|
718
|
+
opencodeModelManagerOptions({
|
|
719
|
+
apiKey: opencodeApiKey,
|
|
720
|
+
baseUrl: this.getProviderBaseUrl("opencode"),
|
|
721
|
+
}),
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
if (isAuthenticated(openrouterApiKey)) {
|
|
725
|
+
options.push(
|
|
726
|
+
openrouterModelManagerOptions({
|
|
727
|
+
apiKey: openrouterApiKey,
|
|
728
|
+
baseUrl: this.getProviderBaseUrl("openrouter"),
|
|
729
|
+
}),
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
if (isAuthenticated(vercelGatewayApiKey)) {
|
|
733
|
+
options.push(
|
|
734
|
+
vercelAiGatewayModelManagerOptions({
|
|
735
|
+
apiKey: vercelGatewayApiKey,
|
|
736
|
+
baseUrl: this.getProviderBaseUrl("vercel-ai-gateway"),
|
|
737
|
+
}),
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
if (isAuthenticated(kimiApiKey)) {
|
|
741
|
+
options.push(
|
|
742
|
+
kimiCodeModelManagerOptions({
|
|
743
|
+
apiKey: kimiApiKey,
|
|
744
|
+
baseUrl: this.getProviderBaseUrl("kimi-code"),
|
|
745
|
+
}),
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
if (isAuthenticated(githubCopilotApiKey)) {
|
|
749
|
+
options.push(
|
|
750
|
+
githubCopilotModelManagerOptions({
|
|
751
|
+
apiKey: githubCopilotApiKey,
|
|
752
|
+
baseUrl: this.getProviderBaseUrl("github-copilot"),
|
|
753
|
+
}),
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
if (isAuthenticated(googleApiKey)) options.push(googleModelManagerOptions({ apiKey: googleApiKey }));
|
|
757
|
+
if (isAuthenticated(cursorApiKey)) {
|
|
758
|
+
options.push(
|
|
759
|
+
cursorModelManagerOptions({
|
|
760
|
+
apiKey: cursorApiKey,
|
|
761
|
+
baseUrl: this.getProviderBaseUrl("cursor"),
|
|
762
|
+
}),
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const antigravityToken = extractGoogleOAuthToken(googleAntigravityApiKey);
|
|
767
|
+
if (isAuthenticated(antigravityToken)) {
|
|
768
|
+
options.push(
|
|
769
|
+
googleAntigravityModelManagerOptions({
|
|
770
|
+
oauthToken: antigravityToken,
|
|
771
|
+
endpoint: this.getProviderBaseUrl("google-antigravity"),
|
|
772
|
+
}),
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
const geminiCliToken = extractGoogleOAuthToken(googleGeminiCliApiKey);
|
|
777
|
+
if (isAuthenticated(geminiCliToken)) {
|
|
778
|
+
options.push(
|
|
779
|
+
googleGeminiCliModelManagerOptions({
|
|
780
|
+
oauthToken: geminiCliToken,
|
|
781
|
+
endpoint: this.getProviderBaseUrl("google-gemini-cli"),
|
|
782
|
+
}),
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
if (isAuthenticated(codexAccessToken)) {
|
|
787
|
+
const codexAccountId = resolveOAuthAccountIdForAccessToken(this.authStorage, "openai-codex", codexAccessToken);
|
|
788
|
+
options.push(
|
|
789
|
+
openaiCodexModelManagerOptions({
|
|
790
|
+
accessToken: codexAccessToken,
|
|
791
|
+
accountId: codexAccountId,
|
|
792
|
+
}),
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
return options;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
async #discoverWithModelManager(options: ModelManagerOptions<Api>): Promise<Model<Api>[]> {
|
|
800
|
+
try {
|
|
801
|
+
const manager = createModelManager(options);
|
|
802
|
+
const result = await manager.refresh("online");
|
|
803
|
+
return result.models;
|
|
804
|
+
} catch (error) {
|
|
805
|
+
logger.warn("model discovery failed for provider", {
|
|
806
|
+
provider: options.providerId,
|
|
807
|
+
error: error instanceof Error ? error.message : String(error),
|
|
808
|
+
});
|
|
809
|
+
return [];
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
534
813
|
async #discoverOllamaModels(providerConfig: DiscoveryProviderConfig): Promise<Model<Api>[]> {
|
|
535
814
|
const endpoint = this.#normalizeOllamaBaseUrl(providerConfig.baseUrl);
|
|
536
815
|
const tagsUrl = `${endpoint}/api/tags`;
|
|
@@ -698,7 +977,7 @@ export class ModelRegistry {
|
|
|
698
977
|
*/
|
|
699
978
|
async getApiKey(model: Model<Api>, sessionId?: string): Promise<string | undefined> {
|
|
700
979
|
if (this.#keylessProviders.has(model.provider)) {
|
|
701
|
-
return
|
|
980
|
+
return kNoAuth;
|
|
702
981
|
}
|
|
703
982
|
return this.authStorage.getApiKey(model.provider, sessionId, { baseUrl: model.baseUrl });
|
|
704
983
|
}
|
|
@@ -708,7 +987,7 @@ export class ModelRegistry {
|
|
|
708
987
|
*/
|
|
709
988
|
async getApiKeyForProvider(provider: string, sessionId?: string, baseUrl?: string): Promise<string | undefined> {
|
|
710
989
|
if (this.#keylessProviders.has(provider)) {
|
|
711
|
-
return
|
|
990
|
+
return kNoAuth;
|
|
712
991
|
}
|
|
713
992
|
return this.authStorage.getApiKey(provider, sessionId, { baseUrl });
|
|
714
993
|
}
|
|
@@ -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-
|
|
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-
|
|
23
|
+
cursor: "claude-sonnet-4-6",
|
|
24
24
|
openrouter: "openai/gpt-5.1-codex",
|
|
25
|
-
"vercel-ai-gateway": "anthropic/claude-
|
|
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,7 +31,7 @@ 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-
|
|
34
|
+
opencode: "claude-sonnet-4-6",
|
|
35
35
|
"kimi-code": "kimi-k2.5",
|
|
36
36
|
};
|
|
37
37
|
|
|
@@ -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
|
}
|
package/src/discovery/agents.ts
CHANGED
|
@@ -1,36 +1,199 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agents (standard) Provider
|
|
3
3
|
*
|
|
4
|
-
* Loads user-level skills from ~/.
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
26
|
-
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
|
+
});
|
package/src/discovery/builtin.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { type CustomTool, toolCapability } from "../capability/tool";
|
|
|
23
23
|
import type { LoadContext, LoadResult } from "../capability/types";
|
|
24
24
|
import { parseFrontmatter } from "../utils/frontmatter";
|
|
25
25
|
import {
|
|
26
|
+
buildRuleFromMarkdown,
|
|
26
27
|
createSourceMeta,
|
|
27
28
|
discoverExtensionModulePaths,
|
|
28
29
|
expandEnvVarsDeep,
|
|
@@ -307,19 +308,8 @@ async function loadRules(ctx: LoadContext): Promise<LoadResult<Rule>> {
|
|
|
307
308
|
const rulesDir = path.join(dir, "rules");
|
|
308
309
|
const result = await loadFilesFromDir<Rule>(ctx, rulesDir, PROVIDER_ID, level, {
|
|
309
310
|
extensions: ["md", "mdc"],
|
|
310
|
-
transform: (name, content, path, source) =>
|
|
311
|
-
|
|
312
|
-
return {
|
|
313
|
-
name: name.replace(/\.(md|mdc)$/, ""),
|
|
314
|
-
path,
|
|
315
|
-
content: body,
|
|
316
|
-
globs: frontmatter.globs as string[] | undefined,
|
|
317
|
-
alwaysApply: frontmatter.alwaysApply as boolean | undefined,
|
|
318
|
-
description: frontmatter.description as string | undefined,
|
|
319
|
-
ttsrTrigger: typeof frontmatter.ttsr_trigger === "string" ? frontmatter.ttsr_trigger : undefined,
|
|
320
|
-
_source: source,
|
|
321
|
-
};
|
|
322
|
-
},
|
|
311
|
+
transform: (name, content, path, source) =>
|
|
312
|
+
buildRuleFromMarkdown(name, content, path, source, { stripNamePattern: /\.(md|mdc)$/ }),
|
|
323
313
|
});
|
|
324
314
|
items.push(...result.items);
|
|
325
315
|
if (result.warnings) warnings.push(...result.warnings);
|