@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.
- package/CHANGELOG.md +46 -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 +311 -21
- package/src/config/model-resolver.ts +5 -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 +38 -0
- package/src/discovery/windsurf.ts +5 -44
- package/src/export/ttsr.ts +324 -54
- 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/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 +23 -6
- package/src/session/agent-session.ts +252 -44
- package/src/session/auth-storage.ts +12 -0
- package/src/tools/bash-skill-urls.ts +177 -0
- package/src/tools/bash.ts +7 -1
- package/src/tools/read.ts +2 -2
|
@@ -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
|
-
|
|
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,
|
|
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
|
|
261
|
-
const override = overrideCompat
|
|
262
|
-
const merged = { ...base, ...override };
|
|
263
|
-
if (
|
|
264
|
-
merged.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 (
|
|
267
|
-
merged.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
|
|
388
|
-
const models =
|
|
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
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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
|
|
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
|
|
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-
|
|
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,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-
|
|
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
|
}
|
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
|
+
});
|