@oh-my-pi/pi-coding-agent 15.10.7 → 15.10.9
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 +27 -0
- package/dist/types/config/model-registry.d.ts +4 -2
- package/dist/types/config/model-resolver.d.ts +2 -0
- package/dist/types/config/settings-schema.d.ts +9 -0
- package/dist/types/extensibility/custom-tools/loader.d.ts +22 -3
- package/dist/types/extensibility/custom-tools/types.d.ts +3 -1
- package/dist/types/extensibility/extensions/index.d.ts +1 -1
- package/dist/types/extensibility/extensions/loader.d.ts +17 -1
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +8 -0
- package/dist/types/mcp/oauth-discovery.d.ts +4 -1
- package/dist/types/mcp/oauth-flow.d.ts +6 -1
- package/dist/types/mcp/transports/stdio.d.ts +12 -0
- package/dist/types/modes/components/custom-editor.d.ts +3 -2
- package/dist/types/sdk.d.ts +42 -2
- package/dist/types/task/executor.d.ts +16 -0
- package/dist/types/tools/fetch.d.ts +2 -1
- package/dist/types/tools/index.d.ts +20 -1
- package/dist/types/tools/report-tool-issue.d.ts +5 -0
- package/dist/types/tui/hyperlink.d.ts +8 -0
- package/dist/types/web/kagi.d.ts +2 -1
- package/dist/types/web/parallel.d.ts +3 -0
- package/dist/types/web/search/providers/anthropic.d.ts +2 -1
- package/dist/types/web/search/providers/base.d.ts +2 -1
- package/dist/types/web/search/providers/brave.d.ts +2 -1
- package/dist/types/web/search/providers/codex.d.ts +2 -1
- package/dist/types/web/search/providers/exa.d.ts +2 -1
- package/dist/types/web/search/providers/gemini.d.ts +2 -1
- package/dist/types/web/search/providers/jina.d.ts +7 -2
- package/dist/types/web/search/providers/kagi.d.ts +7 -2
- package/dist/types/web/search/providers/kimi.d.ts +7 -2
- package/dist/types/web/search/providers/parallel.d.ts +2 -1
- package/dist/types/web/search/providers/perplexity.d.ts +2 -1
- package/dist/types/web/search/providers/searxng.d.ts +2 -1
- package/dist/types/web/search/providers/synthetic.d.ts +7 -3
- package/dist/types/web/search/providers/tavily.d.ts +2 -1
- package/dist/types/web/search/providers/zai.d.ts +2 -1
- package/package.json +9 -9
- package/src/config/model-registry.ts +13 -7
- package/src/config/model-resolver.ts +57 -2
- package/src/config/settings-schema.ts +6 -0
- package/src/extensibility/custom-tools/loader.ts +43 -19
- package/src/extensibility/custom-tools/types.ts +3 -1
- package/src/extensibility/extensions/index.ts +1 -0
- package/src/extensibility/extensions/loader.ts +29 -6
- package/src/extensibility/plugins/legacy-pi-compat.ts +30 -6
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/mcp/oauth-discovery.ts +8 -3
- package/src/mcp/oauth-flow.ts +12 -5
- package/src/mcp/transports/stdio.ts +139 -3
- package/src/modes/components/assistant-message.ts +28 -6
- package/src/modes/components/custom-editor.ts +69 -9
- package/src/modes/components/transcript-container.ts +77 -25
- package/src/modes/controllers/input-controller.ts +1 -1
- package/src/modes/controllers/mcp-command-controller.ts +2 -2
- package/src/sdk.ts +138 -56
- package/src/ssh/ssh-executor.ts +60 -4
- package/src/task/executor.ts +19 -0
- package/src/task/index.ts +4 -0
- package/src/tools/fetch.ts +22 -5
- package/src/tools/image-gen.ts +33 -11
- package/src/tools/index.ts +21 -2
- package/src/tools/report-tool-issue.ts +7 -1
- package/src/tui/hyperlink.ts +27 -3
- package/src/web/kagi.ts +5 -2
- package/src/web/parallel.ts +7 -3
- package/src/web/search/providers/anthropic.ts +5 -1
- package/src/web/search/providers/base.ts +2 -1
- package/src/web/search/providers/brave.ts +5 -2
- package/src/web/search/providers/codex.ts +6 -2
- package/src/web/search/providers/exa.ts +91 -8
- package/src/web/search/providers/gemini.ts +6 -0
- package/src/web/search/providers/jina.ts +15 -5
- package/src/web/search/providers/kagi.ts +9 -2
- package/src/web/search/providers/kimi.ts +18 -4
- package/src/web/search/providers/parallel.ts +6 -2
- package/src/web/search/providers/perplexity.ts +7 -4
- package/src/web/search/providers/searxng.ts +6 -2
- package/src/web/search/providers/synthetic.ts +9 -5
- package/src/web/search/providers/tavily.ts +4 -2
- package/src/web/search/providers/zai.ts +15 -4
|
@@ -4,14 +4,18 @@
|
|
|
4
4
|
* Uses the Jina Reader `s.jina.ai` endpoint to fetch search results with
|
|
5
5
|
* cleaned content.
|
|
6
6
|
*/
|
|
7
|
-
import { type AuthStorage } from "@oh-my-pi/pi-ai";
|
|
7
|
+
import { type AuthStorage, type FetchImpl } from "@oh-my-pi/pi-ai";
|
|
8
8
|
import type { SearchResponse } from "../../../web/search/types";
|
|
9
9
|
import type { SearchParams } from "./base";
|
|
10
10
|
import { SearchProvider } from "./base";
|
|
11
|
+
type SearchParamsWithFetch = SearchParams & {
|
|
12
|
+
fetch?: FetchImpl;
|
|
13
|
+
};
|
|
11
14
|
export interface JinaSearchParams {
|
|
12
15
|
query: string;
|
|
13
16
|
num_results?: number;
|
|
14
17
|
signal?: AbortSignal;
|
|
18
|
+
fetch?: FetchImpl;
|
|
15
19
|
}
|
|
16
20
|
/** Find JINA_API_KEY from environment or .env files. */
|
|
17
21
|
export declare function findApiKey(): string | null;
|
|
@@ -22,5 +26,6 @@ export declare class JinaProvider extends SearchProvider {
|
|
|
22
26
|
readonly id = "jina";
|
|
23
27
|
readonly label = "Jina";
|
|
24
28
|
isAvailable(_authStorage: AuthStorage): boolean;
|
|
25
|
-
search(params:
|
|
29
|
+
search(params: SearchParamsWithFetch): Promise<SearchResponse>;
|
|
26
30
|
}
|
|
31
|
+
export {};
|
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Thin wrapper that adapts shared Kagi API utilities to SearchResponse shape.
|
|
5
5
|
*/
|
|
6
|
-
import type { AuthStorage } from "@oh-my-pi/pi-ai";
|
|
6
|
+
import type { AuthStorage, FetchImpl } from "@oh-my-pi/pi-ai";
|
|
7
7
|
import type { SearchResponse } from "../../../web/search/types";
|
|
8
8
|
import type { SearchParams } from "./base";
|
|
9
9
|
import { SearchProvider } from "./base";
|
|
10
|
+
type SearchParamsWithFetch = SearchParams & {
|
|
11
|
+
fetch?: FetchImpl;
|
|
12
|
+
};
|
|
10
13
|
/** Execute Kagi web search. */
|
|
11
14
|
export declare function searchKagi(params: {
|
|
12
15
|
query: string;
|
|
@@ -15,11 +18,13 @@ export declare function searchKagi(params: {
|
|
|
15
18
|
signal?: AbortSignal;
|
|
16
19
|
authStorage: AuthStorage;
|
|
17
20
|
sessionId?: string;
|
|
21
|
+
fetch?: FetchImpl;
|
|
18
22
|
}): Promise<SearchResponse>;
|
|
19
23
|
/** Search provider for Kagi web search. */
|
|
20
24
|
export declare class KagiProvider extends SearchProvider {
|
|
21
25
|
readonly id = "kagi";
|
|
22
26
|
readonly label = "Kagi";
|
|
23
27
|
isAvailable(authStorage: AuthStorage): boolean;
|
|
24
|
-
search(params:
|
|
28
|
+
search(params: SearchParamsWithFetch): Promise<SearchResponse>;
|
|
25
29
|
}
|
|
30
|
+
export {};
|
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
* Uses Moonshot Kimi Code search API to retrieve web results.
|
|
5
5
|
* Endpoint: POST https://api.kimi.com/coding/v1/search
|
|
6
6
|
*/
|
|
7
|
-
import { type AuthStorage } from "@oh-my-pi/pi-ai";
|
|
7
|
+
import { type AuthStorage, type FetchImpl } from "@oh-my-pi/pi-ai";
|
|
8
8
|
import type { SearchResponse } from "../../../web/search/types";
|
|
9
9
|
import type { SearchParams } from "./base";
|
|
10
10
|
import { SearchProvider } from "./base";
|
|
11
|
+
type SearchParamsWithFetch = SearchParams & {
|
|
12
|
+
fetch?: FetchImpl;
|
|
13
|
+
};
|
|
11
14
|
export interface KimiSearchParams {
|
|
12
15
|
query: string;
|
|
13
16
|
num_results?: number;
|
|
@@ -15,6 +18,7 @@ export interface KimiSearchParams {
|
|
|
15
18
|
signal?: AbortSignal;
|
|
16
19
|
authStorage: AuthStorage;
|
|
17
20
|
sessionId?: string;
|
|
21
|
+
fetch?: FetchImpl;
|
|
18
22
|
}
|
|
19
23
|
/** Execute Kimi web search. */
|
|
20
24
|
export declare function searchKimi(params: KimiSearchParams): Promise<SearchResponse>;
|
|
@@ -23,5 +27,6 @@ export declare class KimiProvider extends SearchProvider {
|
|
|
23
27
|
readonly id = "kimi";
|
|
24
28
|
readonly label = "Kimi";
|
|
25
29
|
isAvailable(authStorage: AuthStorage): boolean;
|
|
26
|
-
search(params:
|
|
30
|
+
search(params: SearchParamsWithFetch): Promise<SearchResponse>;
|
|
27
31
|
}
|
|
32
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AuthStorage } from "@oh-my-pi/pi-ai";
|
|
1
|
+
import { type AuthStorage, type FetchImpl } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import type { SearchResponse } from "../../../web/search/types";
|
|
3
3
|
import type { SearchParams } from "./base";
|
|
4
4
|
import { SearchProvider } from "./base";
|
|
@@ -6,6 +6,7 @@ export declare function searchParallel(params: {
|
|
|
6
6
|
query: string;
|
|
7
7
|
num_results?: number;
|
|
8
8
|
signal?: AbortSignal;
|
|
9
|
+
fetch?: FetchImpl;
|
|
9
10
|
}, authStorage: AuthStorage, sessionId?: string): Promise<SearchResponse>;
|
|
10
11
|
export declare class ParallelProvider extends SearchProvider {
|
|
11
12
|
readonly id = "parallel";
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - API key (`PERPLEXITY_API_KEY`) via `api.perplexity.ai/chat/completions`
|
|
8
8
|
* - Anonymous via `www.perplexity.ai/rest/sse/perplexity_ask`
|
|
9
9
|
*/
|
|
10
|
-
import { type AuthStorage } from "@oh-my-pi/pi-ai";
|
|
10
|
+
import { type AuthStorage, type FetchImpl } from "@oh-my-pi/pi-ai";
|
|
11
11
|
import type { SearchResponse } from "../../../web/search/types";
|
|
12
12
|
import type { SearchParams } from "./base";
|
|
13
13
|
import { SearchProvider } from "./base";
|
|
@@ -25,6 +25,7 @@ export interface PerplexitySearchParams {
|
|
|
25
25
|
num_search_results?: number;
|
|
26
26
|
authStorage: AuthStorage;
|
|
27
27
|
sessionId?: string;
|
|
28
|
+
fetch?: FetchImpl;
|
|
28
29
|
}
|
|
29
30
|
/** Find PERPLEXITY_API_KEY from environment or .env files (also checks PPLX_API_KEY) */
|
|
30
31
|
export declare function findApiKey(): string | null;
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
*
|
|
25
25
|
* Reference: https://docs.searxng.org/dev/search_api.html
|
|
26
26
|
*/
|
|
27
|
-
import type { AuthStorage } from "@oh-my-pi/pi-ai";
|
|
27
|
+
import type { AuthStorage, FetchImpl } from "@oh-my-pi/pi-ai";
|
|
28
28
|
import type { SearchResponse } from "../../../web/search/types";
|
|
29
29
|
import type { SearchParams } from "./base";
|
|
30
30
|
import { SearchProvider } from "./base";
|
|
@@ -34,6 +34,7 @@ export declare function searchSearXNG(params: {
|
|
|
34
34
|
num_results?: number;
|
|
35
35
|
recency?: "day" | "week" | "month" | "year";
|
|
36
36
|
signal?: AbortSignal;
|
|
37
|
+
fetch?: FetchImpl;
|
|
37
38
|
}): Promise<SearchResponse>;
|
|
38
39
|
/** Search provider for SearXNG web search. */
|
|
39
40
|
export declare class SearXNGProvider extends SearchProvider {
|
|
@@ -4,18 +4,22 @@
|
|
|
4
4
|
* Uses Synthetic's zero-data-retention web search API for coding agents.
|
|
5
5
|
* Endpoint: POST https://api.synthetic.new/v2/search
|
|
6
6
|
*/
|
|
7
|
-
import { type AuthStorage } from "@oh-my-pi/pi-ai";
|
|
7
|
+
import { type AuthStorage, type FetchImpl } from "@oh-my-pi/pi-ai";
|
|
8
8
|
import type { SearchResponse } from "../../../web/search/types";
|
|
9
9
|
import type { SearchParams } from "./base";
|
|
10
10
|
import { SearchProvider } from "./base";
|
|
11
|
+
type SearchParamsWithFetch = SearchParams & {
|
|
12
|
+
fetch?: FetchImpl;
|
|
13
|
+
};
|
|
11
14
|
/** Resolve Synthetic API key through the shared auth storage pipeline. */
|
|
12
15
|
export declare function findApiKey(authStorage: AuthStorage, sessionId?: string, signal?: AbortSignal): Promise<string | undefined>;
|
|
13
16
|
/** Execute Synthetic web search. */
|
|
14
|
-
export declare function searchSynthetic(params:
|
|
17
|
+
export declare function searchSynthetic(params: SearchParamsWithFetch): Promise<SearchResponse>;
|
|
15
18
|
/** Search provider for Synthetic. */
|
|
16
19
|
export declare class SyntheticProvider extends SearchProvider {
|
|
17
20
|
readonly id = "synthetic";
|
|
18
21
|
readonly label = "Synthetic";
|
|
19
22
|
isAvailable(authStorage: AuthStorage): boolean;
|
|
20
|
-
search(params:
|
|
23
|
+
search(params: SearchParamsWithFetch): Promise<SearchResponse>;
|
|
21
24
|
}
|
|
25
|
+
export {};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Uses Tavily's agent-focused search API to return structured results with an
|
|
5
5
|
* optional synthesized answer.
|
|
6
6
|
*/
|
|
7
|
-
import { type AuthStorage } from "@oh-my-pi/pi-ai";
|
|
7
|
+
import { type AuthStorage, type FetchImpl } from "@oh-my-pi/pi-ai";
|
|
8
8
|
import type { SearchResponse } from "../../../web/search/types";
|
|
9
9
|
import type { SearchParams } from "./base";
|
|
10
10
|
import { SearchProvider } from "./base";
|
|
@@ -13,6 +13,7 @@ export interface TavilySearchParams {
|
|
|
13
13
|
num_results?: number;
|
|
14
14
|
recency?: "day" | "week" | "month" | "year";
|
|
15
15
|
signal?: AbortSignal;
|
|
16
|
+
fetch?: FetchImpl;
|
|
16
17
|
}
|
|
17
18
|
/** Find Tavily API key through AuthStorage's unified refresh pipeline. */
|
|
18
19
|
export declare function findApiKey(authStorage: AuthStorage, sessionId: string | undefined, signal: AbortSignal | undefined): Promise<string | null>;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Calls Z.AI's remote MCP server (`webSearchPrime`) and adapts results into
|
|
5
5
|
* the unified SearchResponse shape used by the web search tool.
|
|
6
6
|
*/
|
|
7
|
-
import { type AuthStorage } from "@oh-my-pi/pi-ai";
|
|
7
|
+
import { type AuthStorage, type FetchImpl } from "@oh-my-pi/pi-ai";
|
|
8
8
|
import type { SearchResponse } from "../../../web/search/types";
|
|
9
9
|
import type { SearchParams } from "./base";
|
|
10
10
|
import { SearchProvider } from "./base";
|
|
@@ -12,6 +12,7 @@ export interface ZaiSearchParams {
|
|
|
12
12
|
query: string;
|
|
13
13
|
num_results?: number;
|
|
14
14
|
signal?: AbortSignal;
|
|
15
|
+
fetch?: FetchImpl;
|
|
15
16
|
authStorage: AuthStorage;
|
|
16
17
|
sessionId?: string;
|
|
17
18
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "15.10.
|
|
4
|
+
"version": "15.10.9",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -47,14 +47,14 @@
|
|
|
47
47
|
"@agentclientprotocol/sdk": "0.22.1",
|
|
48
48
|
"@babel/parser": "^7.29.7",
|
|
49
49
|
"@mozilla/readability": "^0.6.0",
|
|
50
|
-
"@oh-my-pi/hashline": "15.10.
|
|
51
|
-
"@oh-my-pi/omp-stats": "15.10.
|
|
52
|
-
"@oh-my-pi/pi-agent-core": "15.10.
|
|
53
|
-
"@oh-my-pi/pi-ai": "15.10.
|
|
54
|
-
"@oh-my-pi/pi-mnemopi": "15.10.
|
|
55
|
-
"@oh-my-pi/pi-natives": "15.10.
|
|
56
|
-
"@oh-my-pi/pi-tui": "15.10.
|
|
57
|
-
"@oh-my-pi/pi-utils": "15.10.
|
|
50
|
+
"@oh-my-pi/hashline": "15.10.9",
|
|
51
|
+
"@oh-my-pi/omp-stats": "15.10.9",
|
|
52
|
+
"@oh-my-pi/pi-agent-core": "15.10.9",
|
|
53
|
+
"@oh-my-pi/pi-ai": "15.10.9",
|
|
54
|
+
"@oh-my-pi/pi-mnemopi": "15.10.9",
|
|
55
|
+
"@oh-my-pi/pi-natives": "15.10.9",
|
|
56
|
+
"@oh-my-pi/pi-tui": "15.10.9",
|
|
57
|
+
"@oh-my-pi/pi-utils": "15.10.9",
|
|
58
58
|
"@opentelemetry/api": "^1.9.1",
|
|
59
59
|
"@opentelemetry/context-async-hooks": "^2.7.1",
|
|
60
60
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
|
|
@@ -95,7 +95,7 @@ const STARTUP_MODEL_CACHE_PROVIDER_IDS: readonly string[] = [
|
|
|
95
95
|
...SPECIAL_MODEL_MANAGER_PROVIDER_IDS,
|
|
96
96
|
];
|
|
97
97
|
|
|
98
|
-
import type { ApiKeyResolver } from "@oh-my-pi/pi-ai";
|
|
98
|
+
import type { ApiKeyResolver, FetchImpl } from "@oh-my-pi/pi-ai";
|
|
99
99
|
import { registerOAuthProvider, unregisterOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
|
|
100
100
|
import type { OAuthCredentials, OAuthLoginCallbacks } from "@oh-my-pi/pi-ai/oauth/types";
|
|
101
101
|
import { isRecord, logger } from "@oh-my-pi/pi-utils";
|
|
@@ -927,6 +927,7 @@ export class ModelRegistry {
|
|
|
927
927
|
#runtimeModelManagers: Map<string, { options: ModelManagerOptions<Api>; sourceId: string }> = new Map();
|
|
928
928
|
#rebuildPending: boolean = false;
|
|
929
929
|
#rebuildSuspended: number = 0;
|
|
930
|
+
#fetch: FetchImpl;
|
|
930
931
|
|
|
931
932
|
/**
|
|
932
933
|
* @param authStorage - Auth storage for API key resolution
|
|
@@ -940,7 +941,9 @@ export class ModelRegistry {
|
|
|
940
941
|
constructor(
|
|
941
942
|
readonly authStorage: AuthStorage,
|
|
942
943
|
modelsPath?: string,
|
|
944
|
+
options?: { fetch?: FetchImpl },
|
|
943
945
|
) {
|
|
946
|
+
this.#fetch = options?.fetch ?? fetch;
|
|
944
947
|
this.#modelsConfigFile = ModelsConfigFile.relocate(modelsPath);
|
|
945
948
|
this.#cacheDbPath = modelsPath ? path.join(path.dirname(modelsPath), "models.db") : undefined;
|
|
946
949
|
// Set up fallback resolver for custom provider API keys
|
|
@@ -1629,6 +1632,7 @@ export class ModelRegistry {
|
|
|
1629
1632
|
googleAntigravityModelManagerOptions({
|
|
1630
1633
|
oauthToken,
|
|
1631
1634
|
endpoint: this.getProviderBaseUrl("google-antigravity"),
|
|
1635
|
+
fetch: this.#fetch,
|
|
1632
1636
|
}),
|
|
1633
1637
|
},
|
|
1634
1638
|
{
|
|
@@ -1638,6 +1642,7 @@ export class ModelRegistry {
|
|
|
1638
1642
|
googleGeminiCliModelManagerOptions({
|
|
1639
1643
|
oauthToken,
|
|
1640
1644
|
endpoint: this.getProviderBaseUrl("google-gemini-cli"),
|
|
1645
|
+
fetch: this.#fetch,
|
|
1641
1646
|
}),
|
|
1642
1647
|
},
|
|
1643
1648
|
{
|
|
@@ -1676,6 +1681,7 @@ export class ModelRegistry {
|
|
|
1676
1681
|
descriptor.createModelManagerOptions({
|
|
1677
1682
|
apiKey: isAuthenticated(apiKey) ? apiKey : undefined,
|
|
1678
1683
|
baseUrl: this.getProviderBaseUrl(descriptor.providerId),
|
|
1684
|
+
fetch: this.#fetch,
|
|
1679
1685
|
}),
|
|
1680
1686
|
);
|
|
1681
1687
|
}
|
|
@@ -1727,7 +1733,7 @@ export class ModelRegistry {
|
|
|
1727
1733
|
): Promise<OllamaDiscoveredModelMetadata | null> {
|
|
1728
1734
|
const showUrl = `${endpoint}/api/show`;
|
|
1729
1735
|
try {
|
|
1730
|
-
const response = await fetch(showUrl, {
|
|
1736
|
+
const response = await this.#fetch(showUrl, {
|
|
1731
1737
|
method: "POST",
|
|
1732
1738
|
headers: { ...(headers ?? {}), "Content-Type": "application/json" },
|
|
1733
1739
|
body: JSON.stringify({ model: modelId }),
|
|
@@ -1775,7 +1781,7 @@ export class ModelRegistry {
|
|
|
1775
1781
|
const endpoint = this.#normalizeOllamaBaseUrl(providerConfig.baseUrl);
|
|
1776
1782
|
const tagsUrl = `${endpoint}/api/tags`;
|
|
1777
1783
|
const headers = { ...(providerConfig.headers ?? {}) };
|
|
1778
|
-
const response = await fetch(tagsUrl, {
|
|
1784
|
+
const response = await this.#fetch(tagsUrl, {
|
|
1779
1785
|
headers,
|
|
1780
1786
|
signal: AbortSignal.timeout(250),
|
|
1781
1787
|
});
|
|
@@ -1819,7 +1825,7 @@ export class ModelRegistry {
|
|
|
1819
1825
|
): Promise<LlamaCppDiscoveredServerMetadata | null> {
|
|
1820
1826
|
const propsUrl = `${this.#toLlamaCppNativeBaseUrl(baseUrl)}/props`;
|
|
1821
1827
|
try {
|
|
1822
|
-
const response = await fetch(propsUrl, {
|
|
1828
|
+
const response = await this.#fetch(propsUrl, {
|
|
1823
1829
|
headers,
|
|
1824
1830
|
signal: AbortSignal.timeout(150),
|
|
1825
1831
|
});
|
|
@@ -1850,7 +1856,7 @@ export class ModelRegistry {
|
|
|
1850
1856
|
}
|
|
1851
1857
|
|
|
1852
1858
|
const [response, serverMetadata] = await Promise.all([
|
|
1853
|
-
fetch(modelsUrl, {
|
|
1859
|
+
this.#fetch(modelsUrl, {
|
|
1854
1860
|
headers,
|
|
1855
1861
|
signal: AbortSignal.timeout(250),
|
|
1856
1862
|
}),
|
|
@@ -1902,7 +1908,7 @@ export class ModelRegistry {
|
|
|
1902
1908
|
headers.Authorization = `Bearer ${apiKey}`;
|
|
1903
1909
|
}
|
|
1904
1910
|
|
|
1905
|
-
const response = await fetch(modelsUrl, {
|
|
1911
|
+
const response = await this.#fetch(modelsUrl, {
|
|
1906
1912
|
headers,
|
|
1907
1913
|
signal: AbortSignal.timeout(10_000),
|
|
1908
1914
|
});
|
|
@@ -1964,7 +1970,7 @@ export class ModelRegistry {
|
|
|
1964
1970
|
headers.Authorization = `Bearer ${apiKey}`;
|
|
1965
1971
|
}
|
|
1966
1972
|
|
|
1967
|
-
const response = await fetch(modelsUrl, {
|
|
1973
|
+
const response = await this.#fetch(modelsUrl, {
|
|
1968
1974
|
headers,
|
|
1969
1975
|
signal: AbortSignal.timeout(10_000),
|
|
1970
1976
|
});
|
|
@@ -118,6 +118,43 @@ function cloneModelWithRequestedId(model: Model<Api>, requestedId: string): Mode
|
|
|
118
118
|
};
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
const UPSTREAM_ROUTING_SLUG = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Split a trailing `@<upstream>` provider-routing selector off a model pattern.
|
|
125
|
+
*
|
|
126
|
+
* `openrouter/z-ai/glm-4.7@cerebras` -> base `openrouter/z-ai/glm-4.7`, upstream
|
|
127
|
+
* `cerebras`. A `:thinking` suffix after the slug is kept on the base
|
|
128
|
+
* (`...@cerebras:high` -> base `...:high`). Returns undefined when there is no
|
|
129
|
+
* `@` or the suffix is not a bare provider slug, so model ids that legitimately
|
|
130
|
+
* contain `@` (`claude-opus-4-8@default`, `workers-ai/@cf/...`) are never split.
|
|
131
|
+
*/
|
|
132
|
+
function splitUpstreamRouting(pattern: string): { base: string; upstream: string } | undefined {
|
|
133
|
+
const at = pattern.lastIndexOf("@");
|
|
134
|
+
if (at <= 0) return undefined;
|
|
135
|
+
const rest = pattern.slice(at + 1);
|
|
136
|
+
const colon = rest.indexOf(":");
|
|
137
|
+
const upstream = colon === -1 ? rest : rest.slice(0, colon);
|
|
138
|
+
if (!UPSTREAM_ROUTING_SLUG.test(upstream)) return undefined;
|
|
139
|
+
const trailing = colon === -1 ? "" : rest.slice(colon);
|
|
140
|
+
return { base: pattern.slice(0, at) + trailing, upstream };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** OpenRouter and Vercel AI Gateway are the aggregators that honor per-request upstream routing. */
|
|
144
|
+
function supportsUpstreamRouting(model: Model<Api>): boolean {
|
|
145
|
+
return model.baseUrl.includes("openrouter.ai") || model.baseUrl.includes("ai-gateway.vercel.sh");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Pin a resolved aggregator model to a single upstream provider via its compat routing block. */
|
|
149
|
+
function applyUpstreamRouting(model: Model<Api>, upstream: string): Model<Api> {
|
|
150
|
+
const aggregatorModel = model as Model<"openai-completions">;
|
|
151
|
+
const routing = { only: [upstream] };
|
|
152
|
+
const compat = model.baseUrl.includes("ai-gateway.vercel.sh")
|
|
153
|
+
? { ...aggregatorModel.compat, vercelGatewayRouting: routing }
|
|
154
|
+
: { ...aggregatorModel.compat, openRouterRouting: routing };
|
|
155
|
+
return { ...model, compat } as Model<Api>;
|
|
156
|
+
}
|
|
157
|
+
|
|
121
158
|
const kProviderModelIndex = Symbol("model-resolver.providerIndex");
|
|
122
159
|
type ModelsWithProviderIndex = readonly Model<Api>[] & {
|
|
123
160
|
[kProviderModelIndex]?: Map<string, Model<Api> | null>;
|
|
@@ -442,6 +479,8 @@ export interface ParsedModelResult {
|
|
|
442
479
|
model: Model<Api> | undefined;
|
|
443
480
|
/** Thinking level if explicitly specified in pattern, undefined otherwise */
|
|
444
481
|
thinkingLevel?: ThinkingLevel;
|
|
482
|
+
/** Upstream provider slug from an `@upstream` routing selector, if present. */
|
|
483
|
+
upstream?: string;
|
|
445
484
|
warning: string | undefined;
|
|
446
485
|
explicitThinkingLevel: boolean;
|
|
447
486
|
}
|
|
@@ -523,7 +562,20 @@ export function parseModelPattern(
|
|
|
523
562
|
options?: { allowInvalidThinkingSelectorFallback?: boolean; modelRegistry?: CanonicalModelRegistry },
|
|
524
563
|
): ParsedModelResult {
|
|
525
564
|
const context = buildPreferenceContext(availableModels, preferences);
|
|
526
|
-
|
|
565
|
+
const direct = parseModelPatternWithContext(pattern, availableModels, context, options);
|
|
566
|
+
if (direct.model) return direct;
|
|
567
|
+
|
|
568
|
+
// No direct match: a trailing `@upstream` may be a provider-routing selector.
|
|
569
|
+
// Only honor it when the base resolves to an aggregator model (OpenRouter /
|
|
570
|
+
// Vercel Gateway); otherwise `@` stays part of the id and `direct` stands.
|
|
571
|
+
const routing = splitUpstreamRouting(pattern);
|
|
572
|
+
if (routing) {
|
|
573
|
+
const routed = parseModelPatternWithContext(routing.base, availableModels, context, options);
|
|
574
|
+
if (routed.model && supportsUpstreamRouting(routed.model)) {
|
|
575
|
+
return { ...routed, model: applyUpstreamRouting(routed.model, routing.upstream), upstream: routing.upstream };
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return direct;
|
|
527
579
|
}
|
|
528
580
|
|
|
529
581
|
const PREFIX_MODEL_ROLE = "pi/";
|
|
@@ -1143,7 +1195,7 @@ export function resolveCliModel(options: {
|
|
|
1143
1195
|
}
|
|
1144
1196
|
|
|
1145
1197
|
const candidates = provider ? availableModels.filter(model => model.provider === provider) : availableModels;
|
|
1146
|
-
const { model, thinkingLevel, warning } = parseModelPattern(pattern, candidates, preferences, {
|
|
1198
|
+
const { model, thinkingLevel, warning, upstream } = parseModelPattern(pattern, candidates, preferences, {
|
|
1147
1199
|
allowInvalidThinkingSelectorFallback: false,
|
|
1148
1200
|
modelRegistry,
|
|
1149
1201
|
});
|
|
@@ -1173,6 +1225,9 @@ export function resolveCliModel(options: {
|
|
|
1173
1225
|
}
|
|
1174
1226
|
}
|
|
1175
1227
|
}
|
|
1228
|
+
if (selector !== undefined && upstream) {
|
|
1229
|
+
selector = `${selector}@${upstream}`;
|
|
1230
|
+
}
|
|
1176
1231
|
|
|
1177
1232
|
return {
|
|
1178
1233
|
model,
|
|
@@ -2187,6 +2187,12 @@ export const SETTINGS_SCHEMA = {
|
|
|
2187
2187
|
},
|
|
2188
2188
|
},
|
|
2189
2189
|
|
|
2190
|
+
"bash.enabled": {
|
|
2191
|
+
type: "boolean",
|
|
2192
|
+
default: true,
|
|
2193
|
+
ui: { tab: "tools", label: "Bash", description: "Enable the bash tool for shell command execution" },
|
|
2194
|
+
},
|
|
2195
|
+
|
|
2190
2196
|
// Search and AST tools
|
|
2191
2197
|
"find.enabled": {
|
|
2192
2198
|
type: "boolean",
|
|
@@ -66,8 +66,10 @@ async function loadTool(
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
/** Tool path with optional source metadata
|
|
70
|
-
|
|
69
|
+
/** Tool path with optional source metadata, suitable for forwarding from a
|
|
70
|
+
* parent session to a subagent so the subagent can re-bind tools to its own
|
|
71
|
+
* `CustomToolAPI` without redoing the filesystem scan. */
|
|
72
|
+
export interface ToolPathWithSource {
|
|
71
73
|
path: string;
|
|
72
74
|
source?: { provider: string; providerName: string; level: "user" | "project" };
|
|
73
75
|
}
|
|
@@ -189,26 +191,19 @@ export async function loadCustomTools(
|
|
|
189
191
|
}
|
|
190
192
|
|
|
191
193
|
/**
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
194
|
+
* Collect the absolute tool-source paths to load, without importing or
|
|
195
|
+
* binding factories. Hot path on session startup — the scan walks
|
|
196
|
+
* `.omp/tools/`, `.claude/tools/`, the plugin tree, and any configured paths.
|
|
197
|
+
*
|
|
198
|
+
* Subagents reuse the parent's collected paths via the SDK's
|
|
199
|
+
* `preloadedCustomToolPaths` option, then call `loadCustomTools` themselves
|
|
200
|
+
* so each session re-binds factories with its own session-scoped
|
|
201
|
+
* `CustomToolAPI` (cwd, exec, pushPendingAction, UI).
|
|
196
202
|
*
|
|
197
203
|
* @param configuredPaths - Explicit paths from settings.json and CLI --tool flags
|
|
198
204
|
* @param cwd - Current working directory
|
|
199
|
-
* @param builtInToolNames - Names of built-in tools to check for conflicts
|
|
200
205
|
*/
|
|
201
|
-
export async function
|
|
202
|
-
configuredPaths: string[],
|
|
203
|
-
cwd: string,
|
|
204
|
-
builtInToolNames: string[],
|
|
205
|
-
pushPendingAction?: (action: {
|
|
206
|
-
label: string;
|
|
207
|
-
sourceToolName: string;
|
|
208
|
-
apply(reason: string): Promise<AgentToolResult<unknown>>;
|
|
209
|
-
reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>;
|
|
210
|
-
}) => void,
|
|
211
|
-
) {
|
|
206
|
+
export async function discoverCustomToolPaths(configuredPaths: string[], cwd: string): Promise<ToolPathWithSource[]> {
|
|
212
207
|
const allPathsWithSources: ToolPathWithSource[] = [];
|
|
213
208
|
const seen = new Set<string>();
|
|
214
209
|
|
|
@@ -241,5 +236,34 @@ export async function discoverAndLoadCustomTools(
|
|
|
241
236
|
addPath(resolvePath(configPath, cwd), { provider: "config", providerName: "Config", level: "project" });
|
|
242
237
|
}
|
|
243
238
|
|
|
244
|
-
return
|
|
239
|
+
return allPathsWithSources;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Discover and load tools from standard locations via capability system:
|
|
244
|
+
* 1. User and project tools discovered by capability providers
|
|
245
|
+
* 2. Installed plugins (~/.omp/plugins/node_modules/*)
|
|
246
|
+
* 3. Explicitly configured paths from settings or CLI
|
|
247
|
+
*
|
|
248
|
+
* Composed of {@link discoverCustomToolPaths} (FS scan) + {@link loadCustomTools}
|
|
249
|
+
* (per-session binding). Subagents skip the first step and just call
|
|
250
|
+
* `loadCustomTools` against the parent's collected paths.
|
|
251
|
+
*
|
|
252
|
+
* @param configuredPaths - Explicit paths from settings.json and CLI --tool flags
|
|
253
|
+
* @param cwd - Current working directory
|
|
254
|
+
* @param builtInToolNames - Names of built-in tools to check for conflicts
|
|
255
|
+
*/
|
|
256
|
+
export async function discoverAndLoadCustomTools(
|
|
257
|
+
configuredPaths: string[],
|
|
258
|
+
cwd: string,
|
|
259
|
+
builtInToolNames: string[],
|
|
260
|
+
pushPendingAction?: (action: {
|
|
261
|
+
label: string;
|
|
262
|
+
sourceToolName: string;
|
|
263
|
+
apply(reason: string): Promise<AgentToolResult<unknown>>;
|
|
264
|
+
reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>;
|
|
265
|
+
}) => void,
|
|
266
|
+
) {
|
|
267
|
+
const pathsWithSources = await discoverCustomToolPaths(configuredPaths, cwd);
|
|
268
|
+
return loadCustomTools(pathsWithSources, cwd, builtInToolNames, pushPendingAction);
|
|
245
269
|
}
|
|
@@ -12,7 +12,7 @@ import type {
|
|
|
12
12
|
ToolTier,
|
|
13
13
|
} from "@oh-my-pi/pi-agent-core";
|
|
14
14
|
import type { CompactionResult } from "@oh-my-pi/pi-agent-core/compaction";
|
|
15
|
-
import type { Model, Static, TSchema } from "@oh-my-pi/pi-ai";
|
|
15
|
+
import type { FetchImpl, Model, Static, TSchema } from "@oh-my-pi/pi-ai";
|
|
16
16
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
17
17
|
import type { Rule } from "../../capability/rule";
|
|
18
18
|
import type { ModelRegistry } from "../../config/model-registry";
|
|
@@ -86,6 +86,8 @@ export interface CustomToolContext {
|
|
|
86
86
|
abort(): void;
|
|
87
87
|
/** Settings instance for the current session. Prefer over the global singleton. */
|
|
88
88
|
settings?: Settings;
|
|
89
|
+
/** Fetch implementation for outbound HTTP; defaults to global fetch when omitted. */
|
|
90
|
+
fetch?: FetchImpl;
|
|
89
91
|
/** Whether to auto-approve all destructive tool operations (--auto-approve CLI flag) */
|
|
90
92
|
autoApprove?: boolean;
|
|
91
93
|
}
|
|
@@ -475,16 +475,24 @@ async function discoverExtensionsInDir(dir: string): Promise<string[]> {
|
|
|
475
475
|
|
|
476
476
|
return discovered;
|
|
477
477
|
}
|
|
478
|
-
|
|
479
478
|
/**
|
|
480
|
-
* Discover
|
|
479
|
+
* Discover absolute paths of extensions to load, without importing or
|
|
480
|
+
* binding factories. Hot path on session startup — the scan walks native
|
|
481
|
+
* `.omp`/`.pi` extension capabilities, the installed-plugin tree, and any
|
|
482
|
+
* configured paths.
|
|
483
|
+
*
|
|
484
|
+
* Subagents reuse the parent's collected paths via the SDK's
|
|
485
|
+
* `preloadedExtensionPaths` option, then call {@link loadExtensions} themselves
|
|
486
|
+
* so each session rebuilds Extension instances bound to its OWN
|
|
487
|
+
* `ExtensionAPI` (cwd, eventBus, runtime). Forwarding the parent's
|
|
488
|
+
* `LoadExtensionsResult` directly would reuse handlers/tools/commands that
|
|
489
|
+
* closed over the parent's `cwd` and event bus.
|
|
481
490
|
*/
|
|
482
|
-
export async function
|
|
491
|
+
export async function discoverExtensionPaths(
|
|
483
492
|
configuredPaths: string[],
|
|
484
493
|
cwd: string,
|
|
485
|
-
eventBus?: EventBus,
|
|
486
494
|
disabledExtensionIds: string[] = [],
|
|
487
|
-
): Promise<
|
|
495
|
+
): Promise<string[]> {
|
|
488
496
|
const allPaths: string[] = [];
|
|
489
497
|
const seen = new Set<string>();
|
|
490
498
|
const disabled = new Set(disabledExtensionIds);
|
|
@@ -545,5 +553,20 @@ export async function discoverAndLoadExtensions(
|
|
|
545
553
|
addPath(resolved);
|
|
546
554
|
}
|
|
547
555
|
|
|
548
|
-
return
|
|
556
|
+
return allPaths;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Discover and load extensions from standard locations. Composed of
|
|
561
|
+
* {@link discoverExtensionPaths} (FS scan) + {@link loadExtensions}
|
|
562
|
+
* (per-session binding).
|
|
563
|
+
*/
|
|
564
|
+
export async function discoverAndLoadExtensions(
|
|
565
|
+
configuredPaths: string[],
|
|
566
|
+
cwd: string,
|
|
567
|
+
eventBus?: EventBus,
|
|
568
|
+
disabledExtensionIds: string[] = [],
|
|
569
|
+
): Promise<LoadExtensionsResult> {
|
|
570
|
+
const paths = await discoverExtensionPaths(configuredPaths, cwd, disabledExtensionIds);
|
|
571
|
+
return loadExtensions(paths, cwd, eventBus);
|
|
549
572
|
}
|