@gmickel/gno 1.28.0 → 1.29.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/README.md +46 -20
- package/assets/skill/SKILL.md +33 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.29.0.zip +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.29.0.zip.sha256 +1 -0
- package/browser-extension/dist/chunk-b2zm0jjd.js +50 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/browser-extension/dist/preview.html +1 -1
- package/browser-extension/dist/service-worker.js +6 -6
- package/package.json +1 -1
- package/spec/cli.md +41 -1
- package/spec/db/schema.sql +19 -0
- package/spec/mcp.md +66 -0
- package/spec/output-schemas/ask.schema.json +31 -1
- package/spec/output-schemas/browser-clip-preview.schema.json +30 -0
- package/spec/output-schemas/collection-egress-check.schema.json +91 -0
- package/spec/output-schemas/collection-egress-policy-set.schema.json +56 -0
- package/spec/output-schemas/collection-egress-policy.schema.json +41 -0
- package/spec/output-schemas/context-capsule-v1.schema.json +89 -2
- package/spec/output-schemas/context-capsule-verification.schema.json +1 -1
- package/spec/output-schemas/egress-audit-management.schema.json +88 -0
- package/spec/output-schemas/mcp-http-error.schema.json +113 -2
- package/spec/output-schemas/publish-artifact.schema.json +32 -0
- package/spec/output-schemas/record-import.schema.json +30 -0
- package/spec/output-schemas/retrieval-trace-common.schema.json +31 -0
- package/spec/output-schemas/retrieval-trace-export.schema.json +3 -0
- package/spec/output-schemas/retrieval-trace-qrels.schema.json +3 -0
- package/spec/output-schemas/retrieval-trace-summary.schema.json +4 -0
- package/spec/output-schemas/search-results.schema.json +30 -0
- package/src/app/context-runtime-contract.ts +16 -7
- package/src/cli/commands/ask.ts +7 -0
- package/src/cli/commands/collection/index.ts +5 -0
- package/src/cli/commands/collection/policy.ts +126 -0
- package/src/cli/commands/context-build.ts +5 -0
- package/src/cli/commands/daemon.ts +45 -3
- package/src/cli/commands/egress-audit.ts +68 -0
- package/src/cli/commands/embed.ts +2 -0
- package/src/cli/commands/query.ts +12 -0
- package/src/cli/commands/replay.ts +13 -3
- package/src/cli/commands/vsearch.ts +3 -1
- package/src/cli/program.ts +123 -0
- package/src/config/index.ts +9 -0
- package/src/config/types.ts +50 -0
- package/src/core/browser-clip.ts +16 -1
- package/src/core/collection-egress-policy-projection.ts +28 -0
- package/src/core/collection-egress-policy-service.ts +443 -0
- package/src/core/collection-egress-policy-validation.ts +242 -0
- package/src/core/config-mutation.ts +31 -19
- package/src/core/context-capsule-schema.ts +111 -1
- package/src/core/context-capsule-validation.ts +4 -4
- package/src/core/context-capsule-verification.ts +5 -1
- package/src/core/context-capsule.ts +177 -132
- package/src/core/context-evidence.ts +86 -14
- package/src/core/destination-classifier.ts +402 -0
- package/src/core/egress-audit.ts +239 -0
- package/src/core/egress-authorization.ts +50 -0
- package/src/core/egress-enforcement.ts +264 -0
- package/src/core/egress-policy.ts +440 -0
- package/src/core/egress-provenance.ts +336 -0
- package/src/core/job-manager.ts +37 -0
- package/src/core/network-boundary-inventory.ts +261 -0
- package/src/core/retrieval-qrels.ts +6 -0
- package/src/core/retrieval-replay.ts +18 -4
- package/src/core/retrieval-trace-export.ts +23 -4
- package/src/core/retrieval-trace-management-helpers.ts +1 -0
- package/src/core/retrieval-trace-management-types.ts +3 -0
- package/src/core/retrieval-trace-management.ts +9 -1
- package/src/core/retrieval-trace-session.ts +94 -0
- package/src/core/retrieval-trace.ts +22 -0
- package/src/ingestion/record-adapter.ts +12 -1
- package/src/ingestion/record-container.ts +38 -22
- package/src/ingestion/sync.ts +15 -0
- package/src/ingestion/types.ts +2 -0
- package/src/llm/errors.ts +10 -0
- package/src/llm/http-inference.ts +175 -0
- package/src/llm/http-policy.ts +537 -0
- package/src/llm/httpEmbedding.ts +47 -28
- package/src/llm/httpGeneration.ts +31 -18
- package/src/llm/httpRerank.ts +30 -18
- package/src/llm/index.ts +1 -0
- package/src/llm/nodeLlamaCpp/adapter.ts +55 -20
- package/src/llm/pinned-http-connection.ts +177 -0
- package/src/mcp/context.ts +53 -1
- package/src/mcp/http-egress.ts +220 -0
- package/src/mcp/http-security.ts +32 -21
- package/src/mcp/http-session.ts +13 -2
- package/src/mcp/http-transport.ts +96 -6
- package/src/mcp/sync-egress.ts +24 -0
- package/src/mcp/tools/add-collection.ts +4 -0
- package/src/mcp/tools/ask.ts +1 -1
- package/src/mcp/tools/context.ts +9 -2
- package/src/mcp/tools/egress.ts +247 -0
- package/src/mcp/tools/embed.ts +1 -0
- package/src/mcp/tools/index-cmd.ts +7 -0
- package/src/mcp/tools/index.ts +98 -0
- package/src/mcp/tools/query.ts +10 -0
- package/src/mcp/tools/remove-collection.ts +4 -0
- package/src/mcp/tools/sync.ts +6 -0
- package/src/mcp/tools/trace.ts +7 -1
- package/src/mcp/tools/vsearch.ts +1 -0
- package/src/pipeline/egress-lineage.ts +124 -0
- package/src/pipeline/hybrid.ts +14 -0
- package/src/pipeline/search.ts +12 -0
- package/src/pipeline/types.ts +3 -0
- package/src/pipeline/vsearch.ts +41 -20
- package/src/publish/artifact.ts +31 -3
- package/src/publish/export-service.ts +25 -0
- package/src/sdk/client.ts +119 -2
- package/src/sdk/embed.ts +1 -0
- package/src/sdk/types.ts +33 -0
- package/src/serve/clipper-capture.ts +5 -1
- package/src/serve/closed-json.ts +61 -0
- package/src/serve/config-sync.ts +4 -1
- package/src/serve/context.ts +1 -0
- package/src/serve/doc-events.ts +110 -39
- package/src/serve/public/components/CollectionEgressCheckPanel.tsx +411 -0
- package/src/serve/public/components/CollectionEgressPolicyEditor.tsx +78 -0
- package/src/serve/public/components/CollectionModelDialog.tsx +93 -9
- package/src/serve/public/components/EgressAuditPanel.tsx +278 -0
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/pages/Collections.tsx +3 -0
- package/src/serve/resident-request.ts +80 -3
- package/src/serve/resident-runtime.ts +63 -1
- package/src/serve/routes/api.ts +174 -0
- package/src/serve/routes/clipper.ts +64 -1
- package/src/serve/routes/mcp.ts +6 -0
- package/src/serve/routes/traces.ts +27 -4
- package/src/serve/server.ts +118 -7
- package/src/store/migrations/023-collection-egress-policy.ts +61 -0
- package/src/store/migrations/024-egress-derived-lineage.ts +121 -0
- package/src/store/migrations/025-collection-egress-policy-revision.ts +40 -0
- package/src/store/migrations/index.ts +6 -0
- package/src/store/retrieval-trace-codec.ts +15 -0
- package/src/store/sqlite/adapter.ts +170 -7
- package/src/store/sqlite/change-journal-store.ts +41 -2
- package/src/store/sqlite/egress-audit-store.ts +485 -0
- package/src/store/sqlite/retrieval-trace-management-store.ts +47 -6
- package/src/store/sqlite/retrieval-trace-retention.ts +2 -0
- package/src/store/sqlite/retrieval-trace-rows.ts +8 -1
- package/src/store/sqlite/retrieval-trace-store.ts +104 -5
- package/src/store/types.ts +126 -1
- package/browser-extension/artifacts/gno-browser-clipper-v1.28.0.zip +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.28.0.zip.sha256 +0 -1
- package/browser-extension/dist/chunk-vn5f663b.js +0 -50
package/src/llm/httpEmbedding.ts
CHANGED
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
* @module src/llm/httpEmbedding
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import type { HttpInferenceOptions } from "./http-inference";
|
|
8
9
|
import type { EmbeddingPort, LlmResult } from "./types";
|
|
9
10
|
|
|
10
|
-
import {
|
|
11
|
+
import { EgressDeniedError } from "../core/egress-enforcement";
|
|
12
|
+
import { egressDeniedInferenceError, inferenceFailedError } from "./errors";
|
|
13
|
+
import { requestHttpInference } from "./http-inference";
|
|
11
14
|
|
|
12
15
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
13
16
|
// Types
|
|
@@ -34,11 +37,13 @@ interface OpenAIEmbeddingResponse {
|
|
|
34
37
|
export class HttpEmbedding implements EmbeddingPort {
|
|
35
38
|
private readonly apiUrl: string;
|
|
36
39
|
private readonly modelName: string;
|
|
40
|
+
private readonly requestOptions: HttpInferenceOptions;
|
|
37
41
|
private dims: number | null = null;
|
|
38
42
|
readonly modelUri: string;
|
|
39
43
|
|
|
40
|
-
constructor(modelUri: string) {
|
|
44
|
+
constructor(modelUri: string, requestOptions: HttpInferenceOptions) {
|
|
41
45
|
this.modelUri = modelUri;
|
|
46
|
+
this.requestOptions = requestOptions;
|
|
42
47
|
// Parse URI: http://host:port/v1/embeddings#modelname or just http://host:port
|
|
43
48
|
const hashIndex = modelUri.indexOf("#");
|
|
44
49
|
if (hashIndex > 0) {
|
|
@@ -64,16 +69,20 @@ export class HttpEmbedding implements EmbeddingPort {
|
|
|
64
69
|
|
|
65
70
|
async embed(text: string): Promise<LlmResult<number[]>> {
|
|
66
71
|
try {
|
|
67
|
-
const response = await
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
const response = await requestHttpInference(
|
|
73
|
+
this.apiUrl,
|
|
74
|
+
{
|
|
75
|
+
method: "POST",
|
|
76
|
+
headers: {
|
|
77
|
+
"Content-Type": "application/json",
|
|
78
|
+
},
|
|
79
|
+
body: JSON.stringify({
|
|
80
|
+
input: text,
|
|
81
|
+
model: this.modelName,
|
|
82
|
+
}),
|
|
71
83
|
},
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
model: this.modelName,
|
|
75
|
-
}),
|
|
76
|
-
});
|
|
84
|
+
this.requestOptions
|
|
85
|
+
);
|
|
77
86
|
|
|
78
87
|
if (!response.ok) {
|
|
79
88
|
const errorText = await response.text();
|
|
@@ -108,26 +117,33 @@ export class HttpEmbedding implements EmbeddingPort {
|
|
|
108
117
|
} catch (e) {
|
|
109
118
|
return {
|
|
110
119
|
ok: false,
|
|
111
|
-
error:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
120
|
+
error:
|
|
121
|
+
e instanceof EgressDeniedError
|
|
122
|
+
? egressDeniedInferenceError(e)
|
|
123
|
+
: inferenceFailedError(
|
|
124
|
+
this.modelUri,
|
|
125
|
+
e instanceof Error ? e : new Error(String(e))
|
|
126
|
+
),
|
|
115
127
|
};
|
|
116
128
|
}
|
|
117
129
|
}
|
|
118
130
|
|
|
119
131
|
async embedBatch(texts: string[]): Promise<LlmResult<number[][]>> {
|
|
120
132
|
try {
|
|
121
|
-
const response = await
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
133
|
+
const response = await requestHttpInference(
|
|
134
|
+
this.apiUrl,
|
|
135
|
+
{
|
|
136
|
+
method: "POST",
|
|
137
|
+
headers: {
|
|
138
|
+
"Content-Type": "application/json",
|
|
139
|
+
},
|
|
140
|
+
body: JSON.stringify({
|
|
141
|
+
input: texts,
|
|
142
|
+
model: this.modelName,
|
|
143
|
+
}),
|
|
125
144
|
},
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
model: this.modelName,
|
|
129
|
-
}),
|
|
130
|
-
});
|
|
145
|
+
this.requestOptions
|
|
146
|
+
);
|
|
131
147
|
|
|
132
148
|
if (!response.ok) {
|
|
133
149
|
const errorText = await response.text();
|
|
@@ -168,10 +184,13 @@ export class HttpEmbedding implements EmbeddingPort {
|
|
|
168
184
|
} catch (e) {
|
|
169
185
|
return {
|
|
170
186
|
ok: false,
|
|
171
|
-
error:
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
187
|
+
error:
|
|
188
|
+
e instanceof EgressDeniedError
|
|
189
|
+
? egressDeniedInferenceError(e)
|
|
190
|
+
: inferenceFailedError(
|
|
191
|
+
this.modelUri,
|
|
192
|
+
e instanceof Error ? e : new Error(String(e))
|
|
193
|
+
),
|
|
175
194
|
};
|
|
176
195
|
}
|
|
177
196
|
}
|
|
@@ -5,12 +5,16 @@
|
|
|
5
5
|
* @module src/llm/httpGeneration
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import type { HttpInferenceOptions } from "./http-inference";
|
|
8
9
|
import type { GenerationPort, GenParams, LlmResult } from "./types";
|
|
9
10
|
|
|
11
|
+
import { EgressDeniedError } from "../core/egress-enforcement";
|
|
10
12
|
import {
|
|
13
|
+
egressDeniedInferenceError,
|
|
11
14
|
inferenceFailedError,
|
|
12
15
|
structuredOutputUnavailableError,
|
|
13
16
|
} from "./errors";
|
|
17
|
+
import { requestHttpInference } from "./http-inference";
|
|
14
18
|
|
|
15
19
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
16
20
|
// Types
|
|
@@ -44,11 +48,13 @@ interface OpenAIChatResponse {
|
|
|
44
48
|
export class HttpGeneration implements GenerationPort {
|
|
45
49
|
private readonly apiUrl: string;
|
|
46
50
|
private readonly modelName: string;
|
|
51
|
+
private readonly requestOptions: HttpInferenceOptions;
|
|
47
52
|
readonly modelUri: string;
|
|
48
53
|
readonly structuredOutput = "none" as const;
|
|
49
54
|
|
|
50
|
-
constructor(modelUri: string) {
|
|
55
|
+
constructor(modelUri: string, requestOptions: HttpInferenceOptions) {
|
|
51
56
|
this.modelUri = modelUri;
|
|
57
|
+
this.requestOptions = requestOptions;
|
|
52
58
|
// Parse URI: http://host:port/v1/chat/completions#modelname
|
|
53
59
|
const hashIndex = modelUri.indexOf("#");
|
|
54
60
|
if (hashIndex > 0) {
|
|
@@ -74,20 +80,24 @@ export class HttpGeneration implements GenerationPort {
|
|
|
74
80
|
};
|
|
75
81
|
}
|
|
76
82
|
try {
|
|
77
|
-
const response = await
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
const response = await requestHttpInference(
|
|
84
|
+
this.apiUrl,
|
|
85
|
+
{
|
|
86
|
+
method: "POST",
|
|
87
|
+
headers: {
|
|
88
|
+
"Content-Type": "application/json",
|
|
89
|
+
},
|
|
90
|
+
body: JSON.stringify({
|
|
91
|
+
model: this.modelName,
|
|
92
|
+
messages: [{ role: "user", content: prompt }],
|
|
93
|
+
temperature: params?.temperature ?? 0,
|
|
94
|
+
max_tokens: params?.maxTokens ?? 256,
|
|
95
|
+
stop: params?.stop,
|
|
96
|
+
seed: params?.seed,
|
|
97
|
+
}),
|
|
81
98
|
},
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
messages: [{ role: "user", content: prompt }],
|
|
85
|
-
temperature: params?.temperature ?? 0,
|
|
86
|
-
max_tokens: params?.maxTokens ?? 256,
|
|
87
|
-
stop: params?.stop,
|
|
88
|
-
seed: params?.seed,
|
|
89
|
-
}),
|
|
90
|
-
});
|
|
99
|
+
this.requestOptions
|
|
100
|
+
);
|
|
91
101
|
|
|
92
102
|
if (!response.ok) {
|
|
93
103
|
const errorText = await response.text();
|
|
@@ -107,10 +117,13 @@ export class HttpGeneration implements GenerationPort {
|
|
|
107
117
|
} catch (e) {
|
|
108
118
|
return {
|
|
109
119
|
ok: false,
|
|
110
|
-
error:
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
120
|
+
error:
|
|
121
|
+
e instanceof EgressDeniedError
|
|
122
|
+
? egressDeniedInferenceError(e)
|
|
123
|
+
: inferenceFailedError(
|
|
124
|
+
this.modelUri,
|
|
125
|
+
e instanceof Error ? e : new Error(String(e))
|
|
126
|
+
),
|
|
114
127
|
};
|
|
115
128
|
}
|
|
116
129
|
}
|
package/src/llm/httpRerank.ts
CHANGED
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
* @module src/llm/httpRerank
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import type { HttpInferenceOptions } from "./http-inference";
|
|
8
9
|
import type { LlmResult, RerankPort, RerankScore } from "./types";
|
|
9
10
|
|
|
10
|
-
import {
|
|
11
|
+
import { EgressDeniedError } from "../core/egress-enforcement";
|
|
12
|
+
import { egressDeniedInferenceError, inferenceFailedError } from "./errors";
|
|
13
|
+
import { requestHttpInference } from "./http-inference";
|
|
11
14
|
|
|
12
15
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
13
16
|
// Types
|
|
@@ -36,10 +39,12 @@ export class HttpRerank implements RerankPort {
|
|
|
36
39
|
private readonly apiUrl: string;
|
|
37
40
|
private readonly modelName: string;
|
|
38
41
|
private readonly instruction: string;
|
|
42
|
+
private readonly requestOptions: HttpInferenceOptions;
|
|
39
43
|
readonly modelUri: string;
|
|
40
44
|
|
|
41
|
-
constructor(modelUri: string) {
|
|
45
|
+
constructor(modelUri: string, requestOptions: HttpInferenceOptions) {
|
|
42
46
|
this.modelUri = modelUri;
|
|
47
|
+
this.requestOptions = requestOptions;
|
|
43
48
|
// Parse URI: http://host:port/v1/completions#modelname
|
|
44
49
|
const hashIndex = modelUri.indexOf("#");
|
|
45
50
|
if (hashIndex > 0) {
|
|
@@ -109,19 +114,23 @@ export class HttpRerank implements RerankPort {
|
|
|
109
114
|
|
|
110
115
|
private async scoreBatch(prompts: string[]): Promise<LlmResult<number[]>> {
|
|
111
116
|
try {
|
|
112
|
-
const response = await
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
117
|
+
const response = await requestHttpInference(
|
|
118
|
+
this.apiUrl,
|
|
119
|
+
{
|
|
120
|
+
method: "POST",
|
|
121
|
+
headers: {
|
|
122
|
+
"Content-Type": "application/json",
|
|
123
|
+
},
|
|
124
|
+
body: JSON.stringify({
|
|
125
|
+
model: this.modelName,
|
|
126
|
+
prompt: prompts, // Array of prompts for batching
|
|
127
|
+
max_tokens: 10, // Just need the score
|
|
128
|
+
temperature: 0, // Deterministic
|
|
129
|
+
stop: ["\n", "<"],
|
|
130
|
+
}),
|
|
116
131
|
},
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
prompt: prompts, // Array of prompts for batching
|
|
120
|
-
max_tokens: 10, // Just need the score
|
|
121
|
-
temperature: 0, // Deterministic
|
|
122
|
-
stop: ["\n", "<"],
|
|
123
|
-
}),
|
|
124
|
-
});
|
|
132
|
+
this.requestOptions
|
|
133
|
+
);
|
|
125
134
|
|
|
126
135
|
if (!response.ok) {
|
|
127
136
|
const errorText = await response.text();
|
|
@@ -154,10 +163,13 @@ export class HttpRerank implements RerankPort {
|
|
|
154
163
|
} catch (e) {
|
|
155
164
|
return {
|
|
156
165
|
ok: false,
|
|
157
|
-
error:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
166
|
+
error:
|
|
167
|
+
e instanceof EgressDeniedError
|
|
168
|
+
? egressDeniedInferenceError(e)
|
|
169
|
+
: inferenceFailedError(
|
|
170
|
+
this.modelUri,
|
|
171
|
+
e instanceof Error ? e : new Error(String(e))
|
|
172
|
+
),
|
|
161
173
|
};
|
|
162
174
|
}
|
|
163
175
|
}
|
package/src/llm/index.ts
CHANGED
|
@@ -43,11 +43,22 @@ export interface CreatePortOptions {
|
|
|
43
43
|
policy?: DownloadPolicy;
|
|
44
44
|
/** Progress callback for downloads */
|
|
45
45
|
onProgress?: ProgressCallback;
|
|
46
|
+
/**
|
|
47
|
+
* Participating collection scope for outbound HTTP inference.
|
|
48
|
+
* `"all"` is an explicit corpus-wide decision, never an omission fallback.
|
|
49
|
+
*/
|
|
50
|
+
egressCollections: readonly string[] | "all";
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
/** Default policy: no auto-download (backwards compatible) */
|
|
49
54
|
const DEFAULT_POLICY: DownloadPolicy = { offline: false, allowDownload: false };
|
|
50
55
|
|
|
56
|
+
const resolveEgressCollectionNames = (
|
|
57
|
+
config: Config,
|
|
58
|
+
scope: CreatePortOptions["egressCollections"]
|
|
59
|
+
): readonly string[] =>
|
|
60
|
+
scope === "all" ? config.collections.map(({ name }) => name) : scope;
|
|
61
|
+
|
|
51
62
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
52
63
|
// Adapter
|
|
53
64
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -70,16 +81,22 @@ export class LlmAdapter {
|
|
|
70
81
|
* With options.policy.allowDownload=true, auto-downloads if not cached.
|
|
71
82
|
*/
|
|
72
83
|
async createEmbeddingPort(
|
|
73
|
-
modelUri
|
|
74
|
-
options
|
|
84
|
+
modelUri: string | undefined,
|
|
85
|
+
options: CreatePortOptions
|
|
75
86
|
): Promise<LlmResult<EmbeddingPort>> {
|
|
76
87
|
const preset = getActivePreset(this.config);
|
|
77
88
|
const uri = modelUri ?? preset.embed;
|
|
78
|
-
const policy = options
|
|
89
|
+
const policy = options.policy ?? DEFAULT_POLICY;
|
|
79
90
|
|
|
80
91
|
// Use HTTP embedding for remote endpoints
|
|
81
92
|
if (isHttpModelUri(uri)) {
|
|
82
|
-
const httpEmbed = new HttpEmbedding(uri
|
|
93
|
+
const httpEmbed = new HttpEmbedding(uri, {
|
|
94
|
+
collections: this.config.collections,
|
|
95
|
+
collectionNames: resolveEgressCollectionNames(
|
|
96
|
+
this.config,
|
|
97
|
+
options.egressCollections
|
|
98
|
+
),
|
|
99
|
+
});
|
|
83
100
|
// Initialize to verify connection and get dimensions
|
|
84
101
|
const initResult = await httpEmbed.init();
|
|
85
102
|
if (!initResult.ok) {
|
|
@@ -93,7 +110,7 @@ export class LlmAdapter {
|
|
|
93
110
|
uri,
|
|
94
111
|
"embed",
|
|
95
112
|
policy,
|
|
96
|
-
options
|
|
113
|
+
options.onProgress
|
|
97
114
|
);
|
|
98
115
|
if (!resolved.ok) {
|
|
99
116
|
return resolved;
|
|
@@ -111,15 +128,21 @@ export class LlmAdapter {
|
|
|
111
128
|
* With options.policy.allowDownload=true, auto-downloads if not cached.
|
|
112
129
|
*/
|
|
113
130
|
async createGenerationPort(
|
|
114
|
-
modelUri
|
|
115
|
-
options
|
|
131
|
+
modelUri: string | undefined,
|
|
132
|
+
options: CreatePortOptions
|
|
116
133
|
): Promise<LlmResult<GenerationPort>> {
|
|
117
134
|
const uri = getAnswerModelUri(this.config, modelUri);
|
|
118
|
-
const policy = options
|
|
135
|
+
const policy = options.policy ?? DEFAULT_POLICY;
|
|
119
136
|
|
|
120
137
|
// Use HTTP generation for remote endpoints
|
|
121
138
|
if (isHttpGenUri(uri)) {
|
|
122
|
-
const httpGen = new HttpGeneration(uri
|
|
139
|
+
const httpGen = new HttpGeneration(uri, {
|
|
140
|
+
collections: this.config.collections,
|
|
141
|
+
collectionNames: resolveEgressCollectionNames(
|
|
142
|
+
this.config,
|
|
143
|
+
options.egressCollections
|
|
144
|
+
),
|
|
145
|
+
});
|
|
123
146
|
return { ok: true, value: httpGen };
|
|
124
147
|
}
|
|
125
148
|
|
|
@@ -128,7 +151,7 @@ export class LlmAdapter {
|
|
|
128
151
|
uri,
|
|
129
152
|
"gen",
|
|
130
153
|
policy,
|
|
131
|
-
options
|
|
154
|
+
options.onProgress
|
|
132
155
|
);
|
|
133
156
|
if (!resolved.ok) {
|
|
134
157
|
return resolved;
|
|
@@ -145,14 +168,20 @@ export class LlmAdapter {
|
|
|
145
168
|
* Uses preset.expand when configured, else falls back to preset.gen.
|
|
146
169
|
*/
|
|
147
170
|
async createExpansionPort(
|
|
148
|
-
modelUri
|
|
149
|
-
options
|
|
171
|
+
modelUri: string | undefined,
|
|
172
|
+
options: CreatePortOptions
|
|
150
173
|
): Promise<LlmResult<GenerationPort>> {
|
|
151
174
|
const uri = getExpandModelUri(this.config, modelUri);
|
|
152
|
-
const policy = options
|
|
175
|
+
const policy = options.policy ?? DEFAULT_POLICY;
|
|
153
176
|
|
|
154
177
|
if (isHttpGenUri(uri)) {
|
|
155
|
-
const httpGen = new HttpGeneration(uri
|
|
178
|
+
const httpGen = new HttpGeneration(uri, {
|
|
179
|
+
collections: this.config.collections,
|
|
180
|
+
collectionNames: resolveEgressCollectionNames(
|
|
181
|
+
this.config,
|
|
182
|
+
options.egressCollections
|
|
183
|
+
),
|
|
184
|
+
});
|
|
156
185
|
return { ok: true, value: httpGen };
|
|
157
186
|
}
|
|
158
187
|
|
|
@@ -160,7 +189,7 @@ export class LlmAdapter {
|
|
|
160
189
|
uri,
|
|
161
190
|
"expand",
|
|
162
191
|
policy,
|
|
163
|
-
options
|
|
192
|
+
options.onProgress
|
|
164
193
|
);
|
|
165
194
|
if (!resolved.ok) {
|
|
166
195
|
return resolved;
|
|
@@ -178,16 +207,22 @@ export class LlmAdapter {
|
|
|
178
207
|
* With options.policy.allowDownload=true, auto-downloads if not cached.
|
|
179
208
|
*/
|
|
180
209
|
async createRerankPort(
|
|
181
|
-
modelUri
|
|
182
|
-
options
|
|
210
|
+
modelUri: string | undefined,
|
|
211
|
+
options: CreatePortOptions
|
|
183
212
|
): Promise<LlmResult<RerankPort>> {
|
|
184
213
|
const preset = getActivePreset(this.config);
|
|
185
214
|
const uri = modelUri ?? preset.rerank;
|
|
186
|
-
const policy = options
|
|
215
|
+
const policy = options.policy ?? DEFAULT_POLICY;
|
|
187
216
|
|
|
188
217
|
// Use HTTP rerank for remote endpoints
|
|
189
218
|
if (isHttpRerankUri(uri)) {
|
|
190
|
-
const httpRerank = new HttpRerank(uri
|
|
219
|
+
const httpRerank = new HttpRerank(uri, {
|
|
220
|
+
collections: this.config.collections,
|
|
221
|
+
collectionNames: resolveEgressCollectionNames(
|
|
222
|
+
this.config,
|
|
223
|
+
options.egressCollections
|
|
224
|
+
),
|
|
225
|
+
});
|
|
191
226
|
return { ok: true, value: httpRerank };
|
|
192
227
|
}
|
|
193
228
|
|
|
@@ -196,7 +231,7 @@ export class LlmAdapter {
|
|
|
196
231
|
uri,
|
|
197
232
|
"rerank",
|
|
198
233
|
policy,
|
|
199
|
-
options
|
|
234
|
+
options.onProgress
|
|
200
235
|
);
|
|
201
236
|
if (!resolved.ok) {
|
|
202
237
|
return resolved;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/** Opaque request seam for one DNS-pinned HTTP connection. */
|
|
2
|
+
|
|
3
|
+
import type { DestinationClassificationAudit } from "../core/destination-classifier";
|
|
4
|
+
|
|
5
|
+
export interface HttpDestinationPolicyAudit {
|
|
6
|
+
protocol: "http" | "https" | "unknown";
|
|
7
|
+
port: number | null;
|
|
8
|
+
redirectCount: number;
|
|
9
|
+
classification: DestinationClassificationAudit;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type PinnedHttpFetch = (
|
|
13
|
+
input: string | URL | Request,
|
|
14
|
+
init?: BunFetchRequestInit
|
|
15
|
+
) => Promise<Response>;
|
|
16
|
+
|
|
17
|
+
export class PinnedHttpRequestError extends Error {
|
|
18
|
+
readonly code = "PINNED_HTTP_REQUEST_FAILED";
|
|
19
|
+
readonly aborted: boolean;
|
|
20
|
+
|
|
21
|
+
constructor(aborted: boolean) {
|
|
22
|
+
super(
|
|
23
|
+
aborted ? "Pinned HTTP request aborted" : "Pinned HTTP request failed"
|
|
24
|
+
);
|
|
25
|
+
this.name = "PinnedHttpRequestError";
|
|
26
|
+
this.aborted = aborted;
|
|
27
|
+
this.stack = `${this.name}: ${this.message}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
toJSON(): { code: string; message: string; aborted: boolean } {
|
|
31
|
+
return { code: this.code, message: this.message, aborted: this.aborted };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function freezeAudit(
|
|
36
|
+
audit: HttpDestinationPolicyAudit
|
|
37
|
+
): HttpDestinationPolicyAudit {
|
|
38
|
+
const classification = Object.freeze({
|
|
39
|
+
...audit.classification,
|
|
40
|
+
addressClasses: Object.freeze([...audit.classification.addressClasses]),
|
|
41
|
+
});
|
|
42
|
+
return Object.freeze({ ...audit, classification });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function normalizedRequestError(
|
|
46
|
+
signal?: AbortSignal | null
|
|
47
|
+
): PinnedHttpRequestError {
|
|
48
|
+
return new PinnedHttpRequestError(signal?.aborted === true);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function releaseReaderLock(
|
|
52
|
+
reader: ReadableStreamDefaultReader<Uint8Array>
|
|
53
|
+
): void {
|
|
54
|
+
try {
|
|
55
|
+
reader.releaseLock();
|
|
56
|
+
} catch {
|
|
57
|
+
// The sanitized stream is already terminal; never replace its safe error.
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function sanitizeResponseBody(
|
|
62
|
+
body: ReadableStream<Uint8Array> | null,
|
|
63
|
+
signal?: AbortSignal | null
|
|
64
|
+
): ReadableStream<Uint8Array> | null {
|
|
65
|
+
if (!body) return null;
|
|
66
|
+
const reader = body.getReader();
|
|
67
|
+
return new ReadableStream<Uint8Array>({
|
|
68
|
+
async pull(controller): Promise<void> {
|
|
69
|
+
try {
|
|
70
|
+
const chunk = await reader.read();
|
|
71
|
+
if (chunk.done) {
|
|
72
|
+
releaseReaderLock(reader);
|
|
73
|
+
controller.close();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
controller.enqueue(chunk.value);
|
|
77
|
+
} catch {
|
|
78
|
+
releaseReaderLock(reader);
|
|
79
|
+
controller.error(normalizedRequestError(signal));
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
async cancel(reason): Promise<void> {
|
|
83
|
+
try {
|
|
84
|
+
await reader.cancel(reason);
|
|
85
|
+
releaseReaderLock(reader);
|
|
86
|
+
} catch {
|
|
87
|
+
releaseReaderLock(reader);
|
|
88
|
+
throw normalizedRequestError(signal);
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function buildPinnedRequestInit(
|
|
95
|
+
init: BunFetchRequestInit,
|
|
96
|
+
headers: Headers,
|
|
97
|
+
tls: BunFetchRequestInitTLS
|
|
98
|
+
): BunFetchRequestInit {
|
|
99
|
+
return {
|
|
100
|
+
body: init.body,
|
|
101
|
+
cache: init.cache,
|
|
102
|
+
credentials: init.credentials,
|
|
103
|
+
decompress: init.decompress,
|
|
104
|
+
headers,
|
|
105
|
+
integrity: init.integrity,
|
|
106
|
+
keepalive: init.keepalive,
|
|
107
|
+
method: init.method,
|
|
108
|
+
mode: init.mode,
|
|
109
|
+
priority: init.priority,
|
|
110
|
+
redirect: "manual",
|
|
111
|
+
referrer: init.referrer,
|
|
112
|
+
referrerPolicy: init.referrerPolicy,
|
|
113
|
+
signal: init.signal,
|
|
114
|
+
tls,
|
|
115
|
+
verbose: false,
|
|
116
|
+
window: init.window,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* JSON/log projection is redacted. request() is the only raw target consumer;
|
|
122
|
+
* it preserves Host/SNI while forcing an IP URL and manual redirects.
|
|
123
|
+
*/
|
|
124
|
+
export class PinnedHttpConnection {
|
|
125
|
+
readonly #targetUrl: string;
|
|
126
|
+
readonly #hostHeader: string;
|
|
127
|
+
readonly #tlsServerName?: string;
|
|
128
|
+
readonly #audit: HttpDestinationPolicyAudit;
|
|
129
|
+
|
|
130
|
+
constructor(
|
|
131
|
+
targetUrl: string,
|
|
132
|
+
hostHeader: string,
|
|
133
|
+
tlsServerName: string | undefined,
|
|
134
|
+
audit: HttpDestinationPolicyAudit
|
|
135
|
+
) {
|
|
136
|
+
this.#targetUrl = targetUrl;
|
|
137
|
+
this.#hostHeader = hostHeader;
|
|
138
|
+
this.#tlsServerName = tlsServerName;
|
|
139
|
+
this.#audit = freezeAudit(audit);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
toJSON(): { audit: HttpDestinationPolicyAudit; pinned: true } {
|
|
143
|
+
return { audit: this.#audit, pinned: true };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async request(
|
|
147
|
+
init: BunFetchRequestInit = {},
|
|
148
|
+
fetchFn: PinnedHttpFetch = fetch
|
|
149
|
+
): Promise<Response> {
|
|
150
|
+
const headers = new Headers(init.headers);
|
|
151
|
+
headers.set("host", this.#hostHeader);
|
|
152
|
+
const {
|
|
153
|
+
checkServerIdentity: _ignoredIdentityOverride,
|
|
154
|
+
rejectUnauthorized: _ignoredVerificationOverride,
|
|
155
|
+
serverName: _ignoredServerNameOverride,
|
|
156
|
+
...callerTls
|
|
157
|
+
} = init.tls ?? {};
|
|
158
|
+
const tls = {
|
|
159
|
+
...callerTls,
|
|
160
|
+
rejectUnauthorized: true,
|
|
161
|
+
serverName: this.#tlsServerName,
|
|
162
|
+
};
|
|
163
|
+
try {
|
|
164
|
+
const response = await fetchFn(
|
|
165
|
+
this.#targetUrl,
|
|
166
|
+
buildPinnedRequestInit(init, headers, tls)
|
|
167
|
+
);
|
|
168
|
+
return new Response(sanitizeResponseBody(response.body, init.signal), {
|
|
169
|
+
status: response.status,
|
|
170
|
+
statusText: response.statusText,
|
|
171
|
+
headers: response.headers,
|
|
172
|
+
});
|
|
173
|
+
} catch {
|
|
174
|
+
throw normalizedRequestError(init.signal);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|