@oh-my-pi/pi-coding-agent 13.11.0 → 13.11.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 CHANGED
@@ -2,6 +2,34 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [13.11.1] - 2026-03-13
6
+
7
+ ### Added
8
+
9
+ - Added `llama.cpp` as local provider
10
+ - Added `code_search` tool supporting both Exa and grep.app providers for code snippet and documentation search
11
+ - Added `providers.codeSearch` setting to configure code search provider (exa or grep)
12
+ - Added grep.app integration for public code search with result ranking by context relevance
13
+
14
+ ### Changed
15
+
16
+ - Updated compact diff preview to include line hashes for visibility and integrity verification of unchanged and added lines
17
+ - Modified compact diff preview to track line number synchronization between old and new files when processing insertions and deletions
18
+ - Simplified web search tools: removed `web_search_deep`, `web_search_crawl`, `web_search_linkedin`, and `web_search_company` tools
19
+ - Removed `exa.enableLinkedin` and `exa.enableCompany` settings; LinkedIn and company research are no longer available
20
+ - Refactored code search to use pluggable provider system instead of Exa-only implementation
21
+
22
+ ### Removed
23
+
24
+ - Removed Exa LinkedIn search tool (`exa_linkedin`)
25
+ - Removed Exa company research tool (`exa_company`)
26
+ - Removed Exa deep search tool (`exa_search_deep`)
27
+ - Removed Exa URL crawl tool (`exa_crawl`)
28
+
29
+ ### Fixed
30
+
31
+ - Fixed line number parsing in compact diff preview to handle variable-width line number fields with leading whitespace
32
+
5
33
  ## [13.11.0] - 2026-03-12
6
34
  ### Added
7
35
 
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": "13.11.0",
4
+ "version": "13.11.1",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/can1357/oh-my-pi",
7
7
  "author": "Can Boluk",
@@ -41,12 +41,12 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@mozilla/readability": "^0.6",
44
- "@oh-my-pi/omp-stats": "13.11.0",
45
- "@oh-my-pi/pi-agent-core": "13.11.0",
46
- "@oh-my-pi/pi-ai": "13.11.0",
47
- "@oh-my-pi/pi-natives": "13.11.0",
48
- "@oh-my-pi/pi-tui": "13.11.0",
49
- "@oh-my-pi/pi-utils": "13.11.0",
44
+ "@oh-my-pi/omp-stats": "13.11.1",
45
+ "@oh-my-pi/pi-agent-core": "13.11.1",
46
+ "@oh-my-pi/pi-ai": "13.11.1",
47
+ "@oh-my-pi/pi-natives": "13.11.1",
48
+ "@oh-my-pi/pi-tui": "13.11.1",
49
+ "@oh-my-pi/pi-utils": "13.11.1",
50
50
  "@sinclair/typebox": "^0.34",
51
51
  "@xterm/headless": "^6.0",
52
52
  "ajv": "^8.18",
@@ -160,7 +160,7 @@ const ModelOverrideSchema = Type.Object({
160
160
  type ModelOverride = Static<typeof ModelOverrideSchema>;
161
161
 
162
162
  const ProviderDiscoverySchema = Type.Object({
163
- type: Type.Union([Type.Literal("ollama"), Type.Literal("lm-studio")]),
163
+ type: Type.Union([Type.Literal("ollama"), Type.Literal("llama.cpp"), Type.Literal("lm-studio")]),
164
164
  });
165
165
 
166
166
  const ProviderAuthSchema = Type.Union([Type.Literal("apiKey"), Type.Literal("none")]);
@@ -694,6 +694,19 @@ export class ModelRegistry {
694
694
  });
695
695
  this.#keylessProviders.add("ollama");
696
696
  }
697
+ if (!configuredProviders.has("llama.cpp")) {
698
+ this.#discoverableProviders.push({
699
+ provider: "llama.cpp",
700
+ api: "openai-responses",
701
+ baseUrl: Bun.env.LLAMA_CPP_BASE_URL || "http://127.0.0.1:8080",
702
+ discovery: { type: "llama.cpp" },
703
+ optional: true,
704
+ });
705
+ // Only mark as keyless if no API key is configured
706
+ if (!this.authStorage.hasAuth("llama.cpp")) {
707
+ this.#keylessProviders.add("llama.cpp");
708
+ }
709
+ }
697
710
  if (!configuredProviders.has("lm-studio")) {
698
711
  this.#discoverableProviders.push({
699
712
  provider: "lm-studio",
@@ -851,30 +864,28 @@ export class ModelRegistry {
851
864
  }
852
865
  }
853
866
 
854
- let fetchError: string | undefined;
867
+ const providerId = providerConfig.provider;
868
+ let discoveryError: string | undefined;
855
869
  const fetchDynamicModels = async (): Promise<readonly Model<Api>[] | null> => {
856
870
  try {
857
- const models =
858
- providerConfig.discovery.type === "ollama"
859
- ? await this.#discoverOllamaModels(providerConfig)
860
- : await this.#discoverLmStudioModels(providerConfig);
861
- this.#lastDiscoveryWarnings.delete(providerConfig.provider);
871
+ const models = await this.#discoverModelsByProviderType(providerConfig);
872
+ this.#lastDiscoveryWarnings.delete(providerId);
862
873
  return models;
863
874
  } catch (error) {
864
- fetchError = error instanceof Error ? error.message : String(error);
875
+ discoveryError = error instanceof Error ? error.message : String(error);
865
876
  return null;
866
877
  }
867
878
  };
868
879
 
869
880
  const manager = createModelManager<Api>({
870
- providerId: providerConfig.provider,
881
+ providerId,
871
882
  staticModels: [],
872
883
  cacheDbPath: this.#cacheDbPath,
873
884
  cacheTtlMs: 24 * 60 * 60 * 1000,
874
885
  fetchDynamicModels,
875
886
  });
876
887
  const result = await manager.refresh(strategy);
877
- const status = fetchError
888
+ const status = discoveryError
878
889
  ? result.models.length > 0
879
890
  ? "cached"
880
891
  : "unavailable"
@@ -883,19 +894,30 @@ export class ModelRegistry {
883
894
  : cached
884
895
  ? "cached"
885
896
  : "idle";
886
- this.#providerDiscoveryStates.set(providerConfig.provider, {
887
- provider: providerConfig.provider,
897
+ this.#providerDiscoveryStates.set(providerId, {
898
+ provider: providerId,
888
899
  status,
889
900
  optional: providerConfig.optional ?? false,
890
901
  stale: result.stale || status === "cached",
891
- fetchedAt: fetchError ? cached?.updatedAt : Date.now(),
902
+ fetchedAt: discoveryError ? cached?.updatedAt : Date.now(),
892
903
  models: result.models.map(model => model.id),
893
- error: fetchError,
904
+ error: discoveryError,
894
905
  });
895
- if (fetchError) {
896
- this.#warnProviderDiscoveryFailure(providerConfig, fetchError);
906
+ if (discoveryError) {
907
+ this.#warnProviderDiscoveryFailure(providerConfig, discoveryError);
908
+ }
909
+ return this.#applyProviderModelOverrides(providerId, result.models);
910
+ }
911
+
912
+ #discoverModelsByProviderType(providerConfig: DiscoveryProviderConfig): Promise<Model<Api>[]> {
913
+ switch (providerConfig.discovery.type) {
914
+ case "ollama":
915
+ return this.#discoverOllamaModels(providerConfig);
916
+ case "llama.cpp":
917
+ return this.#discoverLlamaCppModels(providerConfig);
918
+ case "lm-studio":
919
+ return this.#discoverLmStudioModels(providerConfig);
897
920
  }
898
- return this.#applyProviderModelOverrides(providerConfig.provider, result.models);
899
921
  }
900
922
 
901
923
  #warnProviderDiscoveryFailure(providerConfig: DiscoveryProviderConfig, error: string): void {
@@ -1106,6 +1128,53 @@ export class ModelRegistry {
1106
1128
  return this.#applyProviderModelOverrides(providerConfig.provider, discovered);
1107
1129
  }
1108
1130
 
1131
+ async #discoverLlamaCppModels(providerConfig: DiscoveryProviderConfig): Promise<Model<Api>[]> {
1132
+ const baseUrl = this.#normalizeLlamaCppBaseUrl(providerConfig.baseUrl);
1133
+ const modelsUrl = `${baseUrl}/models`;
1134
+
1135
+ const headers: Record<string, string> = { ...(providerConfig.headers ?? {}) };
1136
+ const apiKey = await this.authStorage.getApiKey(providerConfig.provider);
1137
+ if (apiKey && apiKey !== DEFAULT_LOCAL_TOKEN && apiKey !== kNoAuth) {
1138
+ headers.Authorization = `Bearer ${apiKey}`;
1139
+ }
1140
+
1141
+ const response = await fetch(modelsUrl, {
1142
+ headers,
1143
+ signal: AbortSignal.timeout(250),
1144
+ });
1145
+ if (!response.ok) {
1146
+ throw new Error(`HTTP ${response.status} from ${modelsUrl}`);
1147
+ }
1148
+ const payload = (await response.json()) as { data?: Array<{ id: string }> };
1149
+ const models = payload.data ?? [];
1150
+ const discovered: Model<Api>[] = [];
1151
+ for (const item of models) {
1152
+ const id = item.id;
1153
+ if (!id) continue;
1154
+ discovered.push(
1155
+ enrichModelThinking({
1156
+ id,
1157
+ name: id,
1158
+ api: providerConfig.api,
1159
+ provider: providerConfig.provider,
1160
+ baseUrl,
1161
+ reasoning: false,
1162
+ input: ["text"],
1163
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1164
+ contextWindow: 128000,
1165
+ maxTokens: 8192,
1166
+ headers,
1167
+ compat: {
1168
+ supportsStore: false,
1169
+ supportsDeveloperRole: false,
1170
+ supportsReasoningEffort: false,
1171
+ },
1172
+ }),
1173
+ );
1174
+ }
1175
+ return this.#applyProviderModelOverrides(providerConfig.provider, discovered);
1176
+ }
1177
+
1109
1178
  async #discoverLmStudioModels(providerConfig: DiscoveryProviderConfig): Promise<Model<Api>[]> {
1110
1179
  const baseUrl = this.#normalizeLmStudioBaseUrl(providerConfig.baseUrl);
1111
1180
  const modelsUrl = `${baseUrl}/models`;
@@ -1153,6 +1222,18 @@ export class ModelRegistry {
1153
1222
  return this.#applyProviderModelOverrides(providerConfig.provider, discovered);
1154
1223
  }
1155
1224
 
1225
+ #normalizeLlamaCppBaseUrl(baseUrl?: string): string {
1226
+ const defaultBaseUrl = "http://127.0.0.1:8080";
1227
+ const raw = baseUrl || defaultBaseUrl;
1228
+ try {
1229
+ const parsed = new URL(raw);
1230
+ const trimmedPath = parsed.pathname.replace(/\/+$/g, "");
1231
+ return `${parsed.protocol}//${parsed.host}${trimmedPath}`;
1232
+ } catch {
1233
+ return raw;
1234
+ }
1235
+ }
1236
+
1156
1237
  #normalizeLmStudioBaseUrl(baseUrl?: string): string {
1157
1238
  const defaultBaseUrl = "http://127.0.0.1:1234/v1";
1158
1239
  const raw = baseUrl || defaultBaseUrl;
@@ -847,6 +847,17 @@ export const SETTINGS_SCHEMA = {
847
847
  default: "auto",
848
848
  ui: { tab: "services", label: "Web search provider", description: "Provider for web search tool", submenu: true },
849
849
  },
850
+ "providers.codeSearch": {
851
+ type: "enum",
852
+ values: ["grep", "exa"] as const,
853
+ default: "grep",
854
+ ui: {
855
+ tab: "services",
856
+ label: "Code search provider",
857
+ description: "Provider for code search tool",
858
+ submenu: true,
859
+ },
860
+ },
850
861
  "providers.image": {
851
862
  type: "enum",
852
863
  values: ["auto", "gemini", "openrouter"] as const,
@@ -904,16 +915,6 @@ export const SETTINGS_SCHEMA = {
904
915
  default: true,
905
916
  ui: { tab: "services", label: "Exa search", description: "Basic search, deep search, code search, crawl" },
906
917
  },
907
- "exa.enableLinkedin": {
908
- type: "boolean",
909
- default: false,
910
- ui: { tab: "services", label: "Exa LinkedIn", description: "Search LinkedIn for people and companies" },
911
- },
912
- "exa.enableCompany": {
913
- type: "boolean",
914
- default: false,
915
- ui: { tab: "services", label: "Exa company", description: "Comprehensive company research tool" },
916
- },
917
918
  "exa.enableResearcher": {
918
919
  type: "boolean",
919
920
  default: false,
@@ -1429,8 +1430,6 @@ export interface TtsrSettings {
1429
1430
  export interface ExaSettings {
1430
1431
  enabled: boolean;
1431
1432
  enableSearch: boolean;
1432
- enableLinkedin: boolean;
1433
- enableCompany: boolean;
1434
1433
  enableResearcher: boolean;
1435
1434
  enableWebsets: boolean;
1436
1435
  }
@@ -304,6 +304,9 @@ export async function scanSkillsFromDir(
304
304
  const content = await readFile(skillPath);
305
305
  if (!content) return;
306
306
  const { frontmatter, body } = parseFrontmatter(content, { source: skillPath });
307
+ if (frontmatter.enabled === false) {
308
+ return;
309
+ }
307
310
  if (requireDescription && !frontmatter.description) {
308
311
  return;
309
312
  }
package/src/exa/index.ts CHANGED
@@ -9,24 +9,14 @@
9
9
  * - 14 websets tools (CRUD, items, search, enrichment, monitor)
10
10
  */
11
11
  import type { CustomTool } from "../extensibility/custom-tools/types";
12
- import { companyTool } from "./company";
13
- import { linkedinTool } from "./linkedin";
14
12
  import { researcherTools } from "./researcher";
15
13
  import { searchTools } from "./search";
16
14
  import type { ExaRenderDetails } from "./types";
17
15
  import { websetsTools } from "./websets";
18
16
 
19
17
  /** All Exa tools (22 total) - static export for backward compatibility */
20
- export const exaTools: CustomTool<any, ExaRenderDetails>[] = [
21
- ...searchTools,
22
- linkedinTool,
23
- companyTool,
24
- ...researcherTools,
25
- ...websetsTools,
26
- ];
18
+ export const exaTools: CustomTool<any, ExaRenderDetails>[] = [...searchTools, ...researcherTools, ...websetsTools];
27
19
 
28
- export { companyTool } from "./company";
29
- export { linkedinTool } from "./linkedin";
30
20
  export * from "./mcp-client";
31
21
  export { renderExaCall, renderExaResult } from "./render";
32
22
  export { researcherTools } from "./researcher";
package/src/exa/search.ts CHANGED
@@ -82,125 +82,4 @@ Parameters:
82
82
  "web_search_exa",
83
83
  );
84
84
 
85
- /** exa_search_deep - AI-synthesized deep research */
86
- const exaSearchDeepTool = createExaTool(
87
- "exa_search_deep",
88
- "Exa Deep Search",
89
- `Perform AI-synthesized deep research using Exa.
90
-
91
- Returns comprehensive research with synthesized answers and multiple sources.
92
-
93
- Similar parameters to exa_search, optimized for research depth.`,
94
-
95
- Type.Object({
96
- query: Type.String({ description: "Research query" }),
97
- type: Type.Optional(
98
- StringEnum(["keyword", "neural", "auto"], {
99
- description: "Search type - neural (semantic), keyword (exact), or auto",
100
- }),
101
- ),
102
- include_domains: Type.Optional(
103
- Type.Array(Type.String(), {
104
- description: "Only include results from these domains",
105
- }),
106
- ),
107
- exclude_domains: Type.Optional(
108
- Type.Array(Type.String(), {
109
- description: "Exclude results from these domains",
110
- }),
111
- ),
112
- start_published_date: Type.Optional(
113
- Type.String({
114
- description: "Filter results published after this date (ISO 8601 format)",
115
- }),
116
- ),
117
- end_published_date: Type.Optional(
118
- Type.String({
119
- description: "Filter results published before this date (ISO 8601 format)",
120
- }),
121
- ),
122
- use_autoprompt: Type.Optional(
123
- Type.Boolean({
124
- description: "Let Exa optimize your query automatically (default: true)",
125
- }),
126
- ),
127
- text: Type.Optional(
128
- Type.Boolean({
129
- description: "Include page text content in results (costs more, default: false)",
130
- }),
131
- ),
132
- highlights: Type.Optional(
133
- Type.Boolean({
134
- description: "Include highlighted relevant snippets (default: false)",
135
- }),
136
- ),
137
- num_results: Type.Optional(
138
- Type.Number({
139
- description: "Maximum number of results to return (default: 10, max: 100)",
140
- minimum: 1,
141
- maximum: 100,
142
- }),
143
- ),
144
- }),
145
- "web_search_exa",
146
- { transformParams: params => ({ ...params, type: "auto" }) },
147
- );
148
-
149
- /** exa_search_code - Code-focused search */
150
- const exaSearchCodeTool = createExaTool(
151
- "exa_search_code",
152
- "Exa Code Search",
153
- `Search for code examples and technical documentation using Exa.
154
-
155
- Optimized for finding code snippets, API documentation, and technical content.
156
-
157
- Parameters:
158
- - query: Code or technical search query (required)
159
- - code_context: Additional context about what you're looking for`,
160
-
161
- Type.Object({
162
- query: Type.String({ description: "Code or technical search query" }),
163
- code_context: Type.Optional(
164
- Type.String({
165
- description: "Additional context about what you're looking for",
166
- }),
167
- ),
168
- }),
169
- "get_code_context_exa",
170
- );
171
-
172
- /** exa_crawl - URL content extraction */
173
- const exaCrawlTool = createExaTool(
174
- "exa_crawl",
175
- "Exa Crawl",
176
- `Extract content from a specific URL using Exa.
177
-
178
- Returns the page content with optional text and highlights.
179
-
180
- Parameters:
181
- - url: URL to crawl (required)
182
- - text: Include full page text content (default: false)
183
- - highlights: Include highlighted relevant snippets (default: false)`,
184
-
185
- Type.Object({
186
- url: Type.String({ description: "URL to crawl and extract content from" }),
187
- text: Type.Optional(
188
- Type.Boolean({
189
- description: "Include full page text content (default: false)",
190
- }),
191
- ),
192
- highlights: Type.Optional(
193
- Type.Boolean({
194
- description: "Include highlighted relevant snippets (default: false)",
195
- }),
196
- ),
197
- }),
198
- "crawling_exa",
199
- );
200
-
201
- export const searchTools: CustomTool<any, ExaRenderDetails>[] = [
202
- exaSearchTool,
203
- exaSearchDeepTool,
204
- exaSearchCodeTool,
205
- exaCrawlTool,
206
- ];
85
+ export const searchTools: CustomTool<any, ExaRenderDetails>[] = [exaSearchTool];