@nijaru/pi-search 0.0.1 → 0.0.2
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/package.json +5 -6
- package/src/anthropic.ts +10 -3
- package/src/contracts.ts +4 -0
- package/src/gemini.ts +9 -2
- package/src/model-selection.ts +20 -2
- package/src/openai.ts +9 -3
- package/src/search-tool.ts +7 -1
package/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nijaru/pi-search",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Provider-neutral web search and fetching tools for Pi",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Nick Naru <nick@nijaru.com>",
|
|
8
|
-
|
|
9
8
|
"publishConfig": {
|
|
10
9
|
"access": "public"
|
|
11
10
|
},
|
|
@@ -42,16 +41,16 @@
|
|
|
42
41
|
"live:smoke": "bun scripts/live-smoke.ts"
|
|
43
42
|
},
|
|
44
43
|
"devDependencies": {
|
|
45
|
-
"@earendil-works/pi-ai": "0.
|
|
46
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
47
|
-
"@earendil-works/pi-tui": "0.
|
|
44
|
+
"@earendil-works/pi-ai": "0.87.0",
|
|
45
|
+
"@earendil-works/pi-coding-agent": "0.87.0",
|
|
46
|
+
"@earendil-works/pi-tui": "0.87.0",
|
|
48
47
|
"@types/bun": "1.3.14",
|
|
49
48
|
"@types/turndown": "^5.0.6",
|
|
50
49
|
"typebox": "1.3.10",
|
|
51
50
|
"typescript": "7.0.2"
|
|
52
51
|
},
|
|
53
52
|
"peerDependencies": {
|
|
54
|
-
"@earendil-works/pi-ai": "
|
|
53
|
+
"@earendil-works/pi-ai": "*",
|
|
55
54
|
"@earendil-works/pi-coding-agent": "*",
|
|
56
55
|
"@earendil-works/pi-tui": "*",
|
|
57
56
|
"typebox": "*"
|
package/src/anthropic.ts
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
tokenUsage,
|
|
19
19
|
type GroundingPlan,
|
|
20
20
|
} from "./grounding";
|
|
21
|
-
import { modelAuthHeaders, type ModelExecution } from "./model-selection";
|
|
21
|
+
import { hasExplicitHeader, modelAuthHeaders, type ModelExecution } from "./model-selection";
|
|
22
22
|
import { httpSource, objectValue, optionalString, type SearchHttpFetch } from "./provider-http";
|
|
23
23
|
import { validateSearchRequest } from "./search";
|
|
24
24
|
|
|
@@ -125,10 +125,17 @@ function endpointFor(model: ProviderModel, override?: string): string {
|
|
|
125
125
|
function anthropicHeaders(execution: ModelExecution): Readonly<Record<string, string>> {
|
|
126
126
|
const headers = modelAuthHeaders(execution, { bearerApiKey: false });
|
|
127
127
|
const apiKey = execution.auth.apiKey;
|
|
128
|
-
|
|
128
|
+
// An explicit x-api-key (value or null deletion) wins over a derived key.
|
|
129
|
+
if (
|
|
130
|
+
apiKey !== undefined &&
|
|
131
|
+
apiKey.trim().length > 0 &&
|
|
132
|
+
!hasExplicitHeader([execution.model.headers, execution.auth.headers], "x-api-key")
|
|
133
|
+
) {
|
|
134
|
+
headers.set("x-api-key", apiKey);
|
|
135
|
+
}
|
|
136
|
+
if (!headers.has("x-api-key")) {
|
|
129
137
|
throw createProviderError({ provider: "anthropic", kind: "auth", message: "Anthropic authentication returned no API key", retryable: false });
|
|
130
138
|
}
|
|
131
|
-
headers.set("x-api-key", apiKey);
|
|
132
139
|
headers.set("anthropic-version", ANTHROPIC_VERSION);
|
|
133
140
|
return Object.fromEntries(headers.entries());
|
|
134
141
|
}
|
package/src/contracts.ts
CHANGED
|
@@ -55,6 +55,10 @@ export type ProviderAuthResult =
|
|
|
55
55
|
readonly ok: true;
|
|
56
56
|
readonly apiKey?: string;
|
|
57
57
|
readonly headers?: ProviderHeaders;
|
|
58
|
+
/** Provider-resolved endpoint override; wins over the catalog `baseUrl`. */
|
|
59
|
+
readonly baseUrl?: string;
|
|
60
|
+
/** Provider-scoped environment credentials resolved with the request. */
|
|
61
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
58
62
|
}
|
|
59
63
|
| {
|
|
60
64
|
readonly ok: false;
|
package/src/gemini.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { createProviderError } from "./errors";
|
|
|
14
14
|
import { cancelResponseBody } from "./http";
|
|
15
15
|
import { executeGroundedSearch } from "./grounding";
|
|
16
16
|
import { httpSource, objectValue, optionalString, type SearchHttpFetch } from "./provider-http";
|
|
17
|
-
import { modelAuthHeaders, type ModelExecution } from "./model-selection";
|
|
17
|
+
import { hasExplicitHeader, modelAuthHeaders, type ModelExecution } from "./model-selection";
|
|
18
18
|
import { validateSearchRequest } from "./search";
|
|
19
19
|
|
|
20
20
|
export const GEMINI_GENERATE_CONTENT_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta";
|
|
@@ -88,7 +88,14 @@ function endpointFor(model: ProviderModel, override?: string): string {
|
|
|
88
88
|
|
|
89
89
|
function authHeaders(execution: ModelExecution): Readonly<Record<string, string>> {
|
|
90
90
|
const headers = modelAuthHeaders(execution, { bearerApiKey: false });
|
|
91
|
-
|
|
91
|
+
// An explicit x-goog-api-key (value or null deletion) wins over a derived key.
|
|
92
|
+
if (
|
|
93
|
+
execution.auth.apiKey !== undefined &&
|
|
94
|
+
execution.auth.apiKey.trim().length > 0 &&
|
|
95
|
+
!hasExplicitHeader([execution.model.headers, execution.auth.headers], "x-goog-api-key")
|
|
96
|
+
) {
|
|
97
|
+
headers.set("x-goog-api-key", execution.auth.apiKey);
|
|
98
|
+
}
|
|
92
99
|
if (!headers.has("x-goog-api-key")) {
|
|
93
100
|
throw createProviderError({ provider: "gemini", kind: "auth", message: "Gemini authentication returned no API key", retryable: false });
|
|
94
101
|
}
|
package/src/model-selection.ts
CHANGED
|
@@ -109,7 +109,9 @@ async function authenticate(model: ProviderModel, registry: NonNullable<Provider
|
|
|
109
109
|
if (!auth.ok) {
|
|
110
110
|
throw createProviderError({ provider: options.searchProvider, kind: "auth", message: `Pi model authentication is not configured for ${model.id}`, retryable: false });
|
|
111
111
|
}
|
|
112
|
-
|
|
112
|
+
// A provider-resolved endpoint wins over the catalog base URL, matching
|
|
113
|
+
// `ModelRegistry.stream()`; adapters build their request URL from this model.
|
|
114
|
+
return { model: auth.baseUrl ? { ...model, baseUrl: auth.baseUrl } : model, auth };
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
export interface ModelAuthHeaderOptions {
|
|
@@ -125,10 +127,26 @@ export function applyProviderHeaders(headers: Headers, source: ProviderHeaders |
|
|
|
125
127
|
}
|
|
126
128
|
}
|
|
127
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Whether any source declares a header, including a `null` deletion. An explicit
|
|
132
|
+
* `authorization` entry must win over a bearer synthesized from the API key.
|
|
133
|
+
*/
|
|
134
|
+
export function hasExplicitHeader(sources: readonly (ProviderHeaders | undefined)[], name: string): boolean {
|
|
135
|
+
const lower = name.toLowerCase();
|
|
136
|
+
return sources.some((source) => source !== undefined && Object.keys(source).some((key) => key.toLowerCase() === lower));
|
|
137
|
+
}
|
|
138
|
+
|
|
128
139
|
export function modelAuthHeaders(execution: ModelExecution, options: ModelAuthHeaderOptions = {}): Headers {
|
|
129
140
|
const headers = new Headers();
|
|
130
141
|
applyProviderHeaders(headers, execution.model.headers);
|
|
131
142
|
applyProviderHeaders(headers, execution.auth.headers);
|
|
132
|
-
if (
|
|
143
|
+
if (
|
|
144
|
+
options.bearerApiKey !== false &&
|
|
145
|
+
execution.auth.apiKey !== undefined &&
|
|
146
|
+
execution.auth.apiKey.trim().length > 0 &&
|
|
147
|
+
!hasExplicitHeader([execution.model.headers, execution.auth.headers], "authorization")
|
|
148
|
+
) {
|
|
149
|
+
headers.set("authorization", `Bearer ${execution.auth.apiKey}`);
|
|
150
|
+
}
|
|
133
151
|
return headers;
|
|
134
152
|
}
|
package/src/openai.ts
CHANGED
|
@@ -14,7 +14,7 @@ import type {
|
|
|
14
14
|
SearchWarning,
|
|
15
15
|
} from "./contracts";
|
|
16
16
|
import { createProviderError, isProviderError } from "./errors";
|
|
17
|
-
import { applyProviderHeaders } from "./model-selection";
|
|
17
|
+
import { applyProviderHeaders, hasExplicitHeader } from "./model-selection";
|
|
18
18
|
import { cancelResponseBody, readBoundedResponseText } from "./http";
|
|
19
19
|
import { parseProviderRateLimits } from "./provider-http";
|
|
20
20
|
import { validateSearchRequest } from "./search";
|
|
@@ -444,7 +444,9 @@ async function selectSearchExecution(active: ProviderModel | undefined, registry
|
|
|
444
444
|
} catch (error) {
|
|
445
445
|
throw createProviderError({ provider, kind: "auth", message: "Pi model authentication could not be resolved", retryable: false, cause: error });
|
|
446
446
|
}
|
|
447
|
-
|
|
447
|
+
// A provider-resolved endpoint wins over the catalog base URL; the adapter
|
|
448
|
+
// builds its request URL from this model. Mirrors selectModelExecution().
|
|
449
|
+
if (auth.ok) return { model: auth.baseUrl ? { ...selected, baseUrl: auth.baseUrl } : selected, auth };
|
|
448
450
|
if (request.executionModel !== undefined) {
|
|
449
451
|
throw createProviderError({ provider, kind: "auth", message: `Pi model authentication is not configured for ${selected.id}`, retryable: false });
|
|
450
452
|
}
|
|
@@ -776,7 +778,11 @@ export class OpenAIProvider implements Provider {
|
|
|
776
778
|
const headers = new Headers();
|
|
777
779
|
applyProviderHeaders(headers, execution.model.headers);
|
|
778
780
|
applyProviderHeaders(headers, execution.auth.headers);
|
|
779
|
-
if (
|
|
781
|
+
if (
|
|
782
|
+
execution.auth.apiKey !== undefined &&
|
|
783
|
+
execution.auth.apiKey.trim().length > 0 &&
|
|
784
|
+
!hasExplicitHeader([execution.model.headers, execution.auth.headers], "authorization")
|
|
785
|
+
) {
|
|
780
786
|
headers.set("Authorization", `Bearer ${execution.auth.apiKey}`);
|
|
781
787
|
}
|
|
782
788
|
if (!headers.has("authorization")) {
|
package/src/search-tool.ts
CHANGED
|
@@ -174,7 +174,13 @@ export function providerContextFromPi(context: ExtensionContext): ProviderContex
|
|
|
174
174
|
if (selected === undefined) return { ok: false, error: `Pi model ${requested.provider}/${requested.id} is not available` };
|
|
175
175
|
const resolved = await context.modelRegistry.getApiKeyAndHeaders(selected);
|
|
176
176
|
return resolved.ok
|
|
177
|
-
? {
|
|
177
|
+
? {
|
|
178
|
+
ok: true,
|
|
179
|
+
...(resolved.apiKey === undefined ? {} : { apiKey: resolved.apiKey }),
|
|
180
|
+
...(resolved.headers === undefined ? {} : { headers: resolved.headers }),
|
|
181
|
+
...(resolved.baseUrl === undefined ? {} : { baseUrl: resolved.baseUrl }),
|
|
182
|
+
...(resolved.env === undefined ? {} : { env: resolved.env }),
|
|
183
|
+
}
|
|
178
184
|
: { ok: false, error: resolved.error };
|
|
179
185
|
},
|
|
180
186
|
},
|