@oscharko-dev/keiko 0.1.2 → 0.1.4
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 +43 -19
- package/dist/cli/lifecycle.js +1 -1
- package/dist/gateway/config.d.ts +6 -0
- package/dist/gateway/config.js +67 -5
- package/dist/gateway/http.d.ts +2 -0
- package/dist/gateway/http.js +31 -0
- package/dist/gateway/index.d.ts +1 -1
- package/dist/gateway/index.js +1 -1
- package/dist/gateway/openai-adapter.js +9 -4
- package/dist/gateway/types.d.ts +1 -0
- package/dist/harness/session.d.ts +1 -1
- package/dist/harness/session.js +1 -1
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.js +1 -1
- package/dist/tools/types.js +15 -0
- package/dist/ui/csp-hashes.json +6 -6
- package/dist/ui/deps.d.ts +2 -2
- package/dist/ui/gateway-setup.js +132 -22
- package/dist/ui/static/404.html +1 -1
- package/dist/ui/static/_next/static/chunks/44-5a78c4df1a436207.js +1 -0
- package/dist/ui/static/index.html +1 -1
- package/dist/ui/static/index.txt +2 -2
- package/dist/ui/static/launch.html +1 -1
- package/dist/ui/static/launch.txt +2 -2
- package/package.json +1 -1
- package/dist/ui/static/_next/static/chunks/44-534236109b98f09a.js +0 -1
- /package/dist/ui/static/_next/static/{pBbsB1CYvKAPHJwR_PXZP → _up3xgs7rHI2vcDDL0kQ3}/_buildManifest.js +0 -0
- /package/dist/ui/static/_next/static/{pBbsB1CYvKAPHJwR_PXZP → _up3xgs7rHI2vcDDL0kQ3}/_ssgManifest.js +0 -0
package/README.md
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
# Keiko
|
|
2
2
|
|
|
3
|
-
Keiko is a
|
|
3
|
+
Keiko is a governed agentic workspace for knowledge work that learns from experience.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The current npm release starts with local developer-assist workflows for regulated engineering teams. Keiko helps inspect a repository, chat with configured language models, generate reviewable unit tests, investigate bugs, run verification, and keep redacted evidence for human review.
|
|
6
|
+
|
|
7
|
+
Keiko is human-controlled by design. It does not commit, push, open pull requests, merge code, or apply changes without an explicit local action. The manifest-producing surfaces emit redacted evidence for audit.
|
|
8
|
+
|
|
9
|
+
## Vision
|
|
10
|
+
|
|
11
|
+
Keiko's long-term direction is a governed workspace where people can delegate knowledge work to learning agents without giving up control, oversight, or accountability.
|
|
12
|
+
|
|
13
|
+
- **Governed delegation:** agents start with a task, not standing rights.
|
|
14
|
+
- **Harness-first control:** agent actions, tool calls, connector access, approvals, failures, and outcomes flow through one observable control layer.
|
|
15
|
+
- **Keiko Twin:** a governed work representative that can build controlled memory about user preferences, project routines, accepted outcomes, and recurring corrections.
|
|
16
|
+
- **Learning from experience:** Keiko should improve future tool selection, escalation, policy suggestions, and workflow quality from structured evidence and feedback.
|
|
17
|
+
- **Enterprise boundaries:** learning can improve suggestions and routines, but it must never grant itself authority or bypass human and organizational policy.
|
|
18
|
+
|
|
19
|
+
Software engineering is the first use case because repositories, tests, reviews, and tool calls create hard evidence. The product direction is broader: a controlled agentic workspace for enterprise knowledge work.
|
|
6
20
|
|
|
7
21
|
## Requirements
|
|
8
22
|
|
|
@@ -45,8 +59,12 @@ If no model gateway is configured, the UI asks for:
|
|
|
45
59
|
|
|
46
60
|
- Base URL, for example `https://llm-gateway.example.com/v1`
|
|
47
61
|
- API token
|
|
62
|
+
- Optional API-key header, only when your gateway admin provides a custom header
|
|
63
|
+
- Deployment names, only when the gateway cannot expose a reliable model list
|
|
64
|
+
|
|
65
|
+
Keiko calls the gateway model list endpoint, tests discovered chat models with a small chat-completions request, and stores only callable chat models in the local runtime configuration. LiteLLM-compatible gateways can also provide model metadata that lets Keiko filter non-chat models before testing. Credentials stay on the local machine and are not returned to the browser.
|
|
48
66
|
|
|
49
|
-
|
|
67
|
+
For OpenAI-compatible gateways such as LiteLLM, usually leave deployment names empty. For Azure AI Foundry, paste the deployment names you want Keiko to offer in the UI.
|
|
50
68
|
|
|
51
69
|
The UI runs on loopback only. The `--host` option can validate a loopback host value; the server always binds `127.0.0.1`.
|
|
52
70
|
|
|
@@ -89,7 +107,8 @@ The UI can create a local runtime config during first-run setup. For scripted us
|
|
|
89
107
|
{
|
|
90
108
|
"modelId": "example-chat-model",
|
|
91
109
|
"baseUrl": "https://llm-gateway.example.com/v1",
|
|
92
|
-
"apiKey": "replace-me"
|
|
110
|
+
"apiKey": "replace-me",
|
|
111
|
+
"apiKeyHeaderName": "authorization"
|
|
93
112
|
}
|
|
94
113
|
]
|
|
95
114
|
}
|
|
@@ -97,14 +116,18 @@ The UI can create a local runtime config during first-run setup. For scripted us
|
|
|
97
116
|
|
|
98
117
|
Environment variables can override file values:
|
|
99
118
|
|
|
100
|
-
| Variable
|
|
101
|
-
|
|
|
102
|
-
| `KEIKO_CONFIG_FILE`
|
|
103
|
-
| `KEIKO_DEFAULT_BASE_URL`
|
|
104
|
-
| `KEIKO_DEFAULT_API_KEY`
|
|
105
|
-
| `
|
|
106
|
-
| `KEIKO_MODEL_<ID>
|
|
107
|
-
| `
|
|
119
|
+
| Variable | Purpose |
|
|
120
|
+
| -------------------------------------- | --------------------------------- |
|
|
121
|
+
| `KEIKO_CONFIG_FILE` | Path to a gateway config file. |
|
|
122
|
+
| `KEIKO_DEFAULT_BASE_URL` | Fallback gateway base URL. |
|
|
123
|
+
| `KEIKO_DEFAULT_API_KEY` | Fallback gateway API token. |
|
|
124
|
+
| `KEIKO_DEFAULT_API_KEY_HEADER_NAME` | Fallback credential header name. |
|
|
125
|
+
| `KEIKO_MODEL_<ID>_BASE_URL` | Per-model base URL override. |
|
|
126
|
+
| `KEIKO_MODEL_<ID>_API_KEY` | Per-model API token override. |
|
|
127
|
+
| `KEIKO_MODEL_<ID>_API_KEY_HEADER_NAME` | Per-model credential header name. |
|
|
128
|
+
| `KEIKO_UI_PORT` | Local UI port override. |
|
|
129
|
+
|
|
130
|
+
Supported credential headers are `authorization`, `x-litellm-key`, `x-api-key`, and `api-key`.
|
|
108
131
|
|
|
109
132
|
Do not commit gateway config files, API tokens, `.keiko/`, or evidence that contains project-specific review material unless your process explicitly requires it.
|
|
110
133
|
|
|
@@ -129,13 +152,14 @@ Known limits:
|
|
|
129
152
|
|
|
130
153
|
## Troubleshooting
|
|
131
154
|
|
|
132
|
-
| Symptom
|
|
133
|
-
|
|
|
134
|
-
| UI does not open
|
|
135
|
-
| Port is busy
|
|
136
|
-
| No model appears
|
|
137
|
-
| Credential test fails
|
|
138
|
-
|
|
|
155
|
+
| Symptom | Check |
|
|
156
|
+
| ---------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
157
|
+
| UI does not open | Run `npm run keiko:status`, then inspect `.keiko/ui.log`. |
|
|
158
|
+
| Port is busy | Start with `KEIKO_UI_PORT=1984 npm run keiko:start` or stop the process using the port. |
|
|
159
|
+
| No model appears | Reopen Settings, verify the base URL and token, then run the credential test again. |
|
|
160
|
+
| Credential test fails | Confirm the gateway accepts OpenAI-compatible chat-completions requests at the configured base URL. |
|
|
161
|
+
| Custom proxy key fails | Confirm whether your gateway expects `Authorization` or a custom API-key header such as `X-Litellm-Key`. |
|
|
162
|
+
| Stale process state | Run `npm run keiko:stop`, delete `.keiko/ui.pid` if the process is no longer running, then start again. |
|
|
139
163
|
|
|
140
164
|
## Further Reading
|
|
141
165
|
|
package/dist/cli/lifecycle.js
CHANGED
|
@@ -204,7 +204,7 @@ async function cmdStart(options, io, env, deps, cwd) {
|
|
|
204
204
|
return 1;
|
|
205
205
|
}
|
|
206
206
|
child.unref();
|
|
207
|
-
writeFileSync(pidFile(options), `${String(child.pid)}\n`, "utf8");
|
|
207
|
+
writeFileSync(pidFile(options), `${String(child.pid)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
208
208
|
io.out(`Starting Keiko UI on ${healthUrl(options).replace("/api/health", "")} ...\n`);
|
|
209
209
|
const healthy = await waitForHealth(options, child.pid, deps);
|
|
210
210
|
if (healthy) {
|
package/dist/gateway/config.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { CircuitBreakerConfig, GatewayConfig, ModelCapability } from "./types.js";
|
|
2
|
+
export declare const DEFAULT_API_KEY_HEADER_NAME = "authorization";
|
|
3
|
+
export declare const SUPPORTED_API_KEY_HEADER_NAMES: readonly ["authorization", "x-litellm-key", "x-api-key", "api-key"];
|
|
2
4
|
export type EnvSource = Readonly<Record<string, string | undefined>>;
|
|
3
5
|
export interface SafeProviderConfig {
|
|
4
6
|
readonly modelId: string;
|
|
7
|
+
readonly credentialHeaderName: string;
|
|
5
8
|
readonly timeoutMs: number;
|
|
6
9
|
readonly maxRetries: number;
|
|
7
10
|
readonly retryBaseDelayMs: number;
|
|
@@ -11,6 +14,9 @@ export interface SafeGatewayConfig {
|
|
|
11
14
|
readonly circuitBreaker: CircuitBreakerConfig;
|
|
12
15
|
readonly capabilities?: readonly ModelCapability[] | undefined;
|
|
13
16
|
}
|
|
17
|
+
export declare function normalizeApiKeyHeaderName(value: unknown, path: string, fallback?: string): string;
|
|
18
|
+
export declare function apiKeyHeaderValue(headerName: string, apiKey: string): string;
|
|
19
|
+
export declare function validateBaseUrl(baseUrl: string, path: string): void;
|
|
14
20
|
export declare function parseGatewayConfig(raw: unknown, env?: EnvSource): GatewayConfig;
|
|
15
21
|
export declare function loadConfigFromFile(path: string, env?: EnvSource): GatewayConfig;
|
|
16
22
|
export declare function toSafeObject(config: GatewayConfig): SafeGatewayConfig;
|
package/dist/gateway/config.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// API keys are sourced only from environment or the config file, never CLI flags,
|
|
4
4
|
// and are excluded from every serialisation path.
|
|
5
5
|
import { readFileSync } from "node:fs";
|
|
6
|
+
import { isIP } from "node:net";
|
|
6
7
|
import { ConfigInvalidError } from "./errors.js";
|
|
7
8
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
8
9
|
const DEFAULT_MAX_RETRIES = 3;
|
|
@@ -10,6 +11,20 @@ const DEFAULT_RETRY_BASE_DELAY_MS = 500;
|
|
|
10
11
|
const DEFAULT_FAILURE_THRESHOLD = 5;
|
|
11
12
|
const DEFAULT_COOLDOWN_MS = 30_000;
|
|
12
13
|
const DEFAULT_HALF_OPEN_PROBES = 2;
|
|
14
|
+
export const DEFAULT_API_KEY_HEADER_NAME = "authorization";
|
|
15
|
+
const MAX_API_KEY_HEADER_NAME_LENGTH = 64;
|
|
16
|
+
const API_KEY_HEADER_NAME_RE = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/u;
|
|
17
|
+
export const SUPPORTED_API_KEY_HEADER_NAMES = [
|
|
18
|
+
DEFAULT_API_KEY_HEADER_NAME,
|
|
19
|
+
"x-litellm-key",
|
|
20
|
+
"x-api-key",
|
|
21
|
+
"api-key",
|
|
22
|
+
];
|
|
23
|
+
const SUPPORTED_API_KEY_HEADER_NAME_SET = new Set(SUPPORTED_API_KEY_HEADER_NAMES);
|
|
24
|
+
const BEARER_API_KEY_HEADER_NAME_SET = new Set([
|
|
25
|
+
DEFAULT_API_KEY_HEADER_NAME,
|
|
26
|
+
"x-litellm-key",
|
|
27
|
+
]);
|
|
13
28
|
function isRecord(value) {
|
|
14
29
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15
30
|
}
|
|
@@ -58,6 +73,33 @@ function optionalNonEmptyString(value, path, fallback) {
|
|
|
58
73
|
}
|
|
59
74
|
return requireNonEmptyString(value, path);
|
|
60
75
|
}
|
|
76
|
+
export function normalizeApiKeyHeaderName(value, path, fallback = DEFAULT_API_KEY_HEADER_NAME) {
|
|
77
|
+
if (value === undefined) {
|
|
78
|
+
return fallback;
|
|
79
|
+
}
|
|
80
|
+
if (typeof value !== "string") {
|
|
81
|
+
throw new ConfigInvalidError(`${path} must be a string`);
|
|
82
|
+
}
|
|
83
|
+
const headerName = value.trim().toLowerCase();
|
|
84
|
+
if (headerName.length === 0) {
|
|
85
|
+
return fallback;
|
|
86
|
+
}
|
|
87
|
+
if (headerName.length > MAX_API_KEY_HEADER_NAME_LENGTH ||
|
|
88
|
+
!API_KEY_HEADER_NAME_RE.test(headerName)) {
|
|
89
|
+
throw new ConfigInvalidError(`${path} must be a valid HTTP header name`);
|
|
90
|
+
}
|
|
91
|
+
if (!SUPPORTED_API_KEY_HEADER_NAME_SET.has(headerName)) {
|
|
92
|
+
throw new ConfigInvalidError(`${path} must be one of ${SUPPORTED_API_KEY_HEADER_NAMES.join(", ")}`);
|
|
93
|
+
}
|
|
94
|
+
return headerName;
|
|
95
|
+
}
|
|
96
|
+
export function apiKeyHeaderValue(headerName, apiKey) {
|
|
97
|
+
if (BEARER_API_KEY_HEADER_NAME_SET.has(headerName) &&
|
|
98
|
+
!apiKey.toLowerCase().startsWith("bearer ")) {
|
|
99
|
+
return `Bearer ${apiKey}`;
|
|
100
|
+
}
|
|
101
|
+
return apiKey;
|
|
102
|
+
}
|
|
61
103
|
function requireEnum(value, path, allowed) {
|
|
62
104
|
if (typeof value !== "string" || !allowed.includes(value)) {
|
|
63
105
|
throw new ConfigInvalidError(`${path} must be one of ${allowed.join(", ")}`);
|
|
@@ -79,17 +121,32 @@ function resolveSecret(modelId, fileValue, env, suffix) {
|
|
|
79
121
|
const fallback = env[`KEIKO_DEFAULT_${suffix}`];
|
|
80
122
|
return fallback ?? "";
|
|
81
123
|
}
|
|
124
|
+
function resolveApiKeyHeaderName(rawValue, path, modelId, env) {
|
|
125
|
+
const token = envModelToken(modelId);
|
|
126
|
+
const perModelName = `KEIKO_MODEL_${token}_API_KEY_HEADER_NAME`;
|
|
127
|
+
const perModel = env[perModelName];
|
|
128
|
+
if (perModel !== undefined && perModel.length > 0) {
|
|
129
|
+
return normalizeApiKeyHeaderName(perModel, perModelName);
|
|
130
|
+
}
|
|
131
|
+
if (rawValue !== undefined) {
|
|
132
|
+
return normalizeApiKeyHeaderName(rawValue, path);
|
|
133
|
+
}
|
|
134
|
+
return normalizeApiKeyHeaderName(env.KEIKO_DEFAULT_API_KEY_HEADER_NAME, "KEIKO_DEFAULT_API_KEY_HEADER_NAME");
|
|
135
|
+
}
|
|
82
136
|
// Validates a resolved baseUrl for scheme and credential hygiene. Host/IP is
|
|
83
137
|
// intentionally NOT restricted: Keiko addresses private network endpoints
|
|
84
138
|
// (private IPs are a valid, first-class target); this guard is scheme/credential
|
|
85
139
|
// hygiene + defence-in-depth, not host filtering.
|
|
86
140
|
function isLoopbackHost(hostname) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
141
|
+
if (hostname === "localhost" || hostname === "::1" || hostname === "[::1]") {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
// Real IPv4 loopback only. isIP === 4 guarantees a well-formed dotted-quad, so a "127." prefix
|
|
145
|
+
// here is the 127.0.0.0/8 block — never a domain such as "127.evil.com" or "127.0.0.1.evil.com".
|
|
146
|
+
// The WHATWG URL parser has already canonicalised IPv4 shorthand/hex into url.hostname.
|
|
147
|
+
return isIP(hostname) === 4 && hostname.startsWith("127.");
|
|
91
148
|
}
|
|
92
|
-
function validateBaseUrl(baseUrl, path) {
|
|
149
|
+
export function validateBaseUrl(baseUrl, path) {
|
|
93
150
|
let url;
|
|
94
151
|
try {
|
|
95
152
|
url = new URL(baseUrl);
|
|
@@ -100,6 +157,9 @@ function validateBaseUrl(baseUrl, path) {
|
|
|
100
157
|
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
101
158
|
throw new ConfigInvalidError(`${path}.baseUrl must use the http or https scheme`);
|
|
102
159
|
}
|
|
160
|
+
if (url.search !== "" || url.hash !== "") {
|
|
161
|
+
throw new ConfigInvalidError(`${path}.baseUrl must not contain a query string or fragment`);
|
|
162
|
+
}
|
|
103
163
|
if (url.protocol === "http:" && !isLoopbackHost(url.hostname)) {
|
|
104
164
|
throw new ConfigInvalidError(`${path}.baseUrl must use https unless it targets localhost or loopback`);
|
|
105
165
|
}
|
|
@@ -161,6 +221,7 @@ function parseProviderConfig(raw, path, modelId, env) {
|
|
|
161
221
|
modelId,
|
|
162
222
|
baseUrl,
|
|
163
223
|
apiKey,
|
|
224
|
+
apiKeyHeaderName: resolveApiKeyHeaderName(raw.apiKeyHeaderName, `${path}.apiKeyHeaderName`, modelId, env),
|
|
164
225
|
timeoutMs: requirePositiveInt(raw.timeoutMs ?? DEFAULT_TIMEOUT_MS, `${path}.timeoutMs`),
|
|
165
226
|
maxRetries: requireNonNegativeInt(raw.maxRetries ?? DEFAULT_MAX_RETRIES, `${path}.maxRetries`),
|
|
166
227
|
retryBaseDelayMs: requirePositiveInt(raw.retryBaseDelayMs ?? DEFAULT_RETRY_BASE_DELAY_MS, `${path}.retryBaseDelayMs`),
|
|
@@ -233,6 +294,7 @@ export function toSafeObject(config) {
|
|
|
233
294
|
return {
|
|
234
295
|
providers: config.providers.map((provider) => ({
|
|
235
296
|
modelId: provider.modelId,
|
|
297
|
+
credentialHeaderName: provider.apiKeyHeaderName ?? DEFAULT_API_KEY_HEADER_NAME,
|
|
236
298
|
timeoutMs: provider.timeoutMs,
|
|
237
299
|
maxRetries: provider.maxRetries,
|
|
238
300
|
retryBaseDelayMs: provider.retryBaseDelayMs,
|
package/dist/gateway/http.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
export declare const MAX_RESPONSE_BYTES = 10000000;
|
|
1
2
|
export interface GatewayFetchOptions extends RequestInit {
|
|
2
3
|
readonly fetchImpl?: typeof fetch | undefined;
|
|
3
4
|
readonly useCaFallback?: boolean | undefined;
|
|
4
5
|
}
|
|
5
6
|
export declare function isMissingIssuerError(error: unknown): boolean;
|
|
6
7
|
export declare function gatewayFetch(url: string, options?: GatewayFetchOptions): Promise<Response>;
|
|
8
|
+
export declare function readJsonCapped(response: Response, maxBytes?: number): Promise<unknown>;
|
package/dist/gateway/http.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { request as httpsRequest } from "node:https";
|
|
3
3
|
import { rootCertificates } from "node:tls";
|
|
4
|
+
// Caps a single gateway response at 10 MB; real chat completions are far smaller.
|
|
5
|
+
export const MAX_RESPONSE_BYTES = 10_000_000;
|
|
4
6
|
function headersFromNode(headers) {
|
|
5
7
|
const out = new Headers();
|
|
6
8
|
for (const [name, value] of Object.entries(headers)) {
|
|
@@ -80,7 +82,14 @@ function fetchWithCaBundle(url, init) {
|
|
|
80
82
|
signal: init.signal ?? undefined,
|
|
81
83
|
}, (res) => {
|
|
82
84
|
const chunks = [];
|
|
85
|
+
let total = 0;
|
|
83
86
|
res.on("data", (chunk) => {
|
|
87
|
+
total += chunk.length;
|
|
88
|
+
if (total > MAX_RESPONSE_BYTES) {
|
|
89
|
+
req.destroy();
|
|
90
|
+
reject(new Error("gateway response exceeded the size limit"));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
84
93
|
chunks.push(chunk);
|
|
85
94
|
});
|
|
86
95
|
res.on("end", () => {
|
|
@@ -109,3 +118,25 @@ export async function gatewayFetch(url, options = {}) {
|
|
|
109
118
|
throw error;
|
|
110
119
|
}
|
|
111
120
|
}
|
|
121
|
+
export async function readJsonCapped(response, maxBytes = MAX_RESPONSE_BYTES) {
|
|
122
|
+
if (response.body === null) {
|
|
123
|
+
return response.json();
|
|
124
|
+
}
|
|
125
|
+
const reader = response.body.getReader();
|
|
126
|
+
const decoder = new TextDecoder();
|
|
127
|
+
const parts = [];
|
|
128
|
+
let total = 0;
|
|
129
|
+
for (;;) {
|
|
130
|
+
const { done, value } = await reader.read();
|
|
131
|
+
if (done)
|
|
132
|
+
break;
|
|
133
|
+
total += value.byteLength;
|
|
134
|
+
if (total > maxBytes) {
|
|
135
|
+
await reader.cancel();
|
|
136
|
+
throw new Error("response body exceeded the size limit");
|
|
137
|
+
}
|
|
138
|
+
parts.push(decoder.decode(value, { stream: true }));
|
|
139
|
+
}
|
|
140
|
+
parts.push(decoder.decode());
|
|
141
|
+
return JSON.parse(parts.join(""));
|
|
142
|
+
}
|
package/dist/gateway/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type { CircuitBreakerConfig, CircuitBreakerStatus, CircuitState, ChatMessage, Clock, CostClass, FinishReason, GatewayConfig, GatewayRequest, LatencyClass, ModelCapability, ModelKind, ModelProviderConfig, NormalizedResponse, NormalizedToolCall, ProviderAdapter, ResponseFormat, StreamDelta, StreamEvent, ToolDefinition, UsageMetadata, } from "./types.js";
|
|
2
2
|
export { CAPABILITY_REGISTRY, createDefaultChatCapability, findCapability, listCapabilities, selectCheapest, type CapabilityQuery, } from "./capabilities.js";
|
|
3
|
-
export { loadConfigFromFile, parseGatewayConfig, toSafeObject, type EnvSource, type SafeGatewayConfig, type SafeProviderConfig, } from "./config.js";
|
|
3
|
+
export { apiKeyHeaderValue, DEFAULT_API_KEY_HEADER_NAME, loadConfigFromFile, normalizeApiKeyHeaderName, parseGatewayConfig, toSafeObject, validateBaseUrl, type EnvSource, type SafeGatewayConfig, type SafeProviderConfig, } from "./config.js";
|
|
4
4
|
export { Gateway, type GatewayDeps } from "./gateway.js";
|
|
5
5
|
export { OpenAiAdapter, type AdapterDeps } from "./openai-adapter.js";
|
|
6
6
|
export { assertConfiguredModel, findConfiguredCapability, listConfiguredCapabilities, selectConfiguredModel, type ModelSelectionQuery, } from "./model-selection.js";
|
package/dist/gateway/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Public barrel for the model gateway: all types, the Gateway orchestrator, the
|
|
2
2
|
// capability registry helpers, config loaders, and the typed error taxonomy.
|
|
3
3
|
export { CAPABILITY_REGISTRY, createDefaultChatCapability, findCapability, listCapabilities, selectCheapest, } from "./capabilities.js";
|
|
4
|
-
export { loadConfigFromFile, parseGatewayConfig, toSafeObject, } from "./config.js";
|
|
4
|
+
export { apiKeyHeaderValue, DEFAULT_API_KEY_HEADER_NAME, loadConfigFromFile, normalizeApiKeyHeaderName, parseGatewayConfig, toSafeObject, validateBaseUrl, } from "./config.js";
|
|
5
5
|
export { Gateway } from "./gateway.js";
|
|
6
6
|
export { OpenAiAdapter } from "./openai-adapter.js";
|
|
7
7
|
export { assertConfiguredModel, findConfiguredCapability, listConfiguredCapabilities, selectConfiguredModel, } from "./model-selection.js";
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
// with no network I/O and no real time. The raw provider body is never echoed into
|
|
4
4
|
// an error; only a redacted, status-level summary is surfaced.
|
|
5
5
|
import { AuthenticationError, CancelledError, ContextOverflowError, ModelRefusalError, ProviderError, RateLimitError, TimeoutError, TransportError, } from "./errors.js";
|
|
6
|
-
import {
|
|
6
|
+
import { apiKeyHeaderValue, DEFAULT_API_KEY_HEADER_NAME } from "./config.js";
|
|
7
|
+
import { gatewayFetch, readJsonCapped } from "./http.js";
|
|
7
8
|
import { normalizeChatResponse } from "./normalize.js";
|
|
8
9
|
import { redact } from "./redaction.js";
|
|
9
10
|
function buildMessage(message) {
|
|
@@ -116,6 +117,10 @@ function mapHttpError(response, modelId, secrets, payload) {
|
|
|
116
117
|
}
|
|
117
118
|
throw new ProviderError(`provider returned HTTP ${String(response.status)} for '${modelId}'`, response.status, secrets);
|
|
118
119
|
}
|
|
120
|
+
function apiKeyHeaders(config) {
|
|
121
|
+
const headerName = config.apiKeyHeaderName ?? DEFAULT_API_KEY_HEADER_NAME;
|
|
122
|
+
return { [headerName]: apiKeyHeaderValue(headerName, config.apiKey) };
|
|
123
|
+
}
|
|
119
124
|
export class OpenAiAdapter {
|
|
120
125
|
deps;
|
|
121
126
|
now;
|
|
@@ -149,7 +154,7 @@ export class OpenAiAdapter {
|
|
|
149
154
|
const body = JSON.stringify(buildBody(request));
|
|
150
155
|
const headers = {
|
|
151
156
|
"content-type": "application/json",
|
|
152
|
-
|
|
157
|
+
...apiKeyHeaders(config),
|
|
153
158
|
};
|
|
154
159
|
try {
|
|
155
160
|
return await gatewayFetch(url, {
|
|
@@ -178,7 +183,7 @@ export class OpenAiAdapter {
|
|
|
178
183
|
}
|
|
179
184
|
async readBody(response, config, secrets) {
|
|
180
185
|
try {
|
|
181
|
-
return await response
|
|
186
|
+
return await readJsonCapped(response);
|
|
182
187
|
}
|
|
183
188
|
catch {
|
|
184
189
|
throw new TransportError(`provider sent an unreadable body for '${config.modelId}'`, secrets);
|
|
@@ -186,7 +191,7 @@ export class OpenAiAdapter {
|
|
|
186
191
|
}
|
|
187
192
|
async readErrorBody(response) {
|
|
188
193
|
try {
|
|
189
|
-
return await response
|
|
194
|
+
return await readJsonCapped(response);
|
|
190
195
|
}
|
|
191
196
|
catch {
|
|
192
197
|
return null;
|
package/dist/gateway/types.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface ModelProviderConfig {
|
|
|
19
19
|
readonly modelId: string;
|
|
20
20
|
readonly baseUrl: string;
|
|
21
21
|
readonly apiKey: string;
|
|
22
|
+
readonly apiKeyHeaderName?: string | undefined;
|
|
22
23
|
readonly timeoutMs: number;
|
|
23
24
|
readonly maxRetries: number;
|
|
24
25
|
readonly retryBaseDelayMs: number;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Clock } from "../gateway/types.js";
|
|
2
2
|
import type { EventSink, Fingerprinter, IdSource, ModelPort, ToolPort } from "./ports.js";
|
|
3
3
|
import { type HarnessLimits, type RunResult, type TaskInput } from "./types.js";
|
|
4
|
-
export declare const HARNESS_VERSION = "0.1.
|
|
4
|
+
export declare const HARNESS_VERSION = "0.1.4";
|
|
5
5
|
export interface AgentConfig {
|
|
6
6
|
readonly model: string;
|
|
7
7
|
readonly workingDirectory: string;
|
package/dist/harness/session.js
CHANGED
|
@@ -10,7 +10,7 @@ import { runLoop } from "./loop.js";
|
|
|
10
10
|
import { MemoryEventSink } from "./sinks.js";
|
|
11
11
|
import { resolveTaskPlan } from "./tasks/policy.js";
|
|
12
12
|
import { DEFAULT_LIMITS, } from "./types.js";
|
|
13
|
-
export const HARNESS_VERSION = "0.1.
|
|
13
|
+
export const HARNESS_VERSION = "0.1.4";
|
|
14
14
|
function resolveLimits(config) {
|
|
15
15
|
return { ...DEFAULT_LIMITS, ...config.limits };
|
|
16
16
|
}
|
package/dist/sdk/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "0.1.
|
|
1
|
+
export declare const SDK_VERSION = "0.1.4";
|
|
2
2
|
export { createSession, type AgentConfig, type AgentSession, type HarnessDeps, type RunResult, type TaskInput, type TaskType, } from "../harness/index.js";
|
|
3
3
|
export { runAgent, type SdkAgentConfig, type SdkEvidenceOptions } from "./run-agent.js";
|
|
4
4
|
export { buildWorkspaceSummary, detectWorkspace, summarizeForAudit, type AuditEntry, type AuditSummary, type ContextEntrySummary, type ContextPackSummary, type WorkspaceInfo, type WorkspaceSummary, } from "../workspace/index.js";
|
package/dist/sdk/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Single-sourced package version; CLI and SDK both read this to avoid drift.
|
|
2
|
-
export const SDK_VERSION = "0.1.
|
|
2
|
+
export const SDK_VERSION = "0.1.4";
|
|
3
3
|
// The typed agent surface. AgentConfig, the session factory, the run result, and the
|
|
4
4
|
// session handle all live in the harness module (ADR-0004); the SDK re-exports them so
|
|
5
5
|
// callers import the agent API from one place.
|
package/dist/tools/types.js
CHANGED
|
@@ -75,6 +75,21 @@ export const DEFAULT_COMMAND_RULES = Object.freeze([
|
|
|
75
75
|
"--namespace",
|
|
76
76
|
"--exec-path",
|
|
77
77
|
]),
|
|
78
|
+
// Deny git's code-execution / external-driver flags. `git -c diff.external=<cmd> diff` (and
|
|
79
|
+
// --config-env/--ext-diff/--textconv) make git spawn an arbitrary command via its OWN shell,
|
|
80
|
+
// defeating the Node spawn's shell:false; --exec-path redirects git to attacker-supplied sub-binaries.
|
|
81
|
+
// hasDeniedFlag runs BEFORE subcommand resolution and matches both `--flag value` and
|
|
82
|
+
// `--flag=value`. `-C`/--git-dir/--work-tree stay value-flags (location only, not execution).
|
|
83
|
+
denyFlags: Object.freeze([
|
|
84
|
+
"-c",
|
|
85
|
+
"--config-env",
|
|
86
|
+
"--exec-path",
|
|
87
|
+
"--ext-diff",
|
|
88
|
+
"--textconv",
|
|
89
|
+
"--no-index",
|
|
90
|
+
"--output",
|
|
91
|
+
"--contents",
|
|
92
|
+
]),
|
|
78
93
|
},
|
|
79
94
|
]);
|
|
80
95
|
export const DEFAULT_PATCH_LIMITS = {
|
package/dist/ui/csp-hashes.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
[
|
|
2
|
-
"'sha256-/XgSSpwWKLLBpmcNR5yfmhouC89Mx4saOeSdHGSlLwA='",
|
|
3
2
|
"'sha256-6JBR+C4qigE40tMbninopqoSguR9H/AhsfWgllRr6y0='",
|
|
4
|
-
"'sha256-9YWu7RzXwUVkhu2CKTX5ddtKfcMNvJTmDtfvbtgQVgs='",
|
|
5
3
|
"'sha256-FhLHRUQz4c4ntLU9VkfEesX7PnzNLENSe/16Hi523Kk='",
|
|
6
|
-
"'sha256-
|
|
4
|
+
"'sha256-M+Hl3si599YRqfbrXwUJ7SE0nbdc4ewUl5kYEqTyPkY='",
|
|
7
5
|
"'sha256-NMmsYxPlvKu6BMNDUuiUA/0HWXXhODWSkUJ3CrerHAI='",
|
|
8
6
|
"'sha256-OBTN3RiyCV4Bq7dFqZ5a2pAXjnCcCYeTJMO2I/LYKeo='",
|
|
9
|
-
"'sha256-
|
|
7
|
+
"'sha256-QNiMmKndkFV2DCDiccdY8YAY8jMbQzYQN++NcsQqdeI='",
|
|
8
|
+
"'sha256-RU8ypFPeRhJ9iyjWdA0gQuMIMNNMmDfpfTvZ8mGo34w='",
|
|
9
|
+
"'sha256-RgGBVXhSba32fqKyKK1LJ4hBlUyuzsh4X4Wz8X/2ViQ='",
|
|
10
10
|
"'sha256-U9W+ZoRW19rf6ohEfUh2oSN8UmJ8mZjCoxp31AbEGYM='",
|
|
11
|
+
"'sha256-Zh9WKxSpBbdbuXpYaiD8KL+3eIzvCyeUVc9aNk+MlSs='",
|
|
11
12
|
"'sha256-bg+CWjI8RppcgHYH6RuW4z4OnLAUEUPDXRoYUo9Tyok='",
|
|
12
13
|
"'sha256-qBQ7RdQKJEJuW7Fj1MbGjDbF6lnRdfu+KV0V4A5MTRg='",
|
|
13
14
|
"'sha256-qjuzziE6xLU3Cras89VlShlRYHgYZuOxceXUDmuvClo='",
|
|
14
15
|
"'sha256-xLP5QIbvR88RAxDKoSWqs6CVxNIRu17hhr7S/Q6hlU0='",
|
|
15
|
-
"'sha256-xz80fPjhAczg/tByXnm3xfZrdAUWODPmQtD4solyj1c='"
|
|
16
|
-
"'sha256-ygPaPap9QVGIk2na6327APzhMLDCt5Hh0UV/LPlFWNc='"
|
|
16
|
+
"'sha256-xz80fPjhAczg/tByXnm3xfZrdAUWODPmQtD4solyj1c='"
|
|
17
17
|
]
|
package/dist/ui/deps.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ export interface UiHandlerDeps {
|
|
|
28
28
|
readonly browser?: BrowserSessionManager | undefined;
|
|
29
29
|
readonly gatewayConfig?: RuntimeGatewayConfig | undefined;
|
|
30
30
|
readonly gatewaySetupTester?: ((config: GatewayConfig, candidateModelIds: readonly string[]) => Promise<readonly string[]>) | undefined;
|
|
31
|
-
readonly gatewayModelDiscovery?: ((baseUrl: string, apiKey: string) => Promise<readonly string[]>) | undefined;
|
|
31
|
+
readonly gatewayModelDiscovery?: ((baseUrl: string, apiKey: string, apiKeyHeaderName?: string) => Promise<readonly string[]>) | undefined;
|
|
32
32
|
}
|
|
33
33
|
export interface BuildHandlerDepsOptions {
|
|
34
34
|
readonly configPath: string | undefined;
|
|
@@ -39,7 +39,7 @@ export interface BuildHandlerDepsOptions {
|
|
|
39
39
|
readonly uiDbPath?: string | undefined;
|
|
40
40
|
readonly store?: UiStore | undefined;
|
|
41
41
|
readonly gatewaySetupTester?: ((config: GatewayConfig, candidateModelIds: readonly string[]) => Promise<readonly string[]>) | undefined;
|
|
42
|
-
readonly gatewayModelDiscovery?: ((baseUrl: string, apiKey: string) => Promise<readonly string[]>) | undefined;
|
|
42
|
+
readonly gatewayModelDiscovery?: ((baseUrl: string, apiKey: string, apiKeyHeaderName?: string) => Promise<readonly string[]>) | undefined;
|
|
43
43
|
}
|
|
44
44
|
export declare function currentGatewayConfig(deps: UiHandlerDeps): GatewayConfig | undefined;
|
|
45
45
|
export declare function currentGatewayConfigPresent(deps: UiHandlerDeps): boolean;
|