@cr1ms0n/pi-subagent 0.9.0 → 0.10.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/CHANGELOG.md +16 -2
- package/README.md +130 -672
- package/README.zh-CN.md +132 -0
- package/docs/DEVELOPMENT.md +124 -0
- package/docs/PLAN.md +2 -0
- package/docs/REFERENCE.md +436 -0
- package/docs/RELEASING.md +151 -32
- package/docs/ROADMAP.md +2 -0
- package/docs/SECURITY.md +17 -18
- package/package.json +9 -1
- package/skills/subagent/SKILL.md +8 -4
- package/src/config.ts +1 -1
- package/src/jev-router.ts +7 -11
- package/src/routing-policy.ts +26 -18
- package/src/routing-types.ts +4 -4
package/docs/SECURITY.md
CHANGED
|
@@ -6,11 +6,11 @@ and can use tools according to their capability profile.
|
|
|
6
6
|
|
|
7
7
|
## What subagents can do
|
|
8
8
|
|
|
9
|
-
| Profile |
|
|
10
|
-
|
|
11
|
-
| `explore` |
|
|
12
|
-
| `review` |
|
|
13
|
-
| `general` | Jev-chosen subset of the full available locally permitted catalog
|
|
9
|
+
| Profile | Finalized tools | Writes? |
|
|
10
|
+
|---------|-----------------|---------|
|
|
11
|
+
| `explore` | Jev-chosen subset of locally permitted read-only candidates, plus available Pi context tools | No project-file writes |
|
|
12
|
+
| `review` | Same as explore | No project-file writes |
|
|
13
|
+
| `general` | Jev-chosen subset of the full available locally permitted catalog, plus available Pi context tools | Yes if write-capable tools are selected |
|
|
14
14
|
|
|
15
15
|
Parallel mode defaults to `explore` to avoid concurrent shared writes.
|
|
16
16
|
|
|
@@ -50,20 +50,20 @@ Parallel mode defaults to `explore` to avoid concurrent shared writes.
|
|
|
50
50
|
(RPC mode). Extension UI dialogs raised inside a child are auto-cancelled so
|
|
51
51
|
they can never hang a run — which also means a child can never obtain
|
|
52
52
|
interactive consent. Prefer restricted tools for async/background runs.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
7. **Steering messages** (`action: "steer"` and the overlay `s` key) inject text
|
|
54
|
+
into a running child's conversation with user-level authority. Anything that
|
|
55
|
+
can call the subagent tool can steer any live run in the same session.
|
|
56
|
+
8. **Process cleanup.** On POSIX, children run in their own process group so tree
|
|
57
57
|
kills work for ordinary descendants. Parent (re)start reaps orphans recorded
|
|
58
58
|
under `~/.pi/subagent-locks/runs/` so resume cannot race a still-alive writer.
|
|
59
59
|
Grandchildren that call `setsid()` can still escape a simple process-group kill.
|
|
60
|
-
|
|
60
|
+
9. **Resume exclusivity.** Direct resume takes a durable per-session file lock;
|
|
61
61
|
concurrent parents cannot append to the same child session.
|
|
62
|
-
|
|
62
|
+
10. **Profiles are tool-selection policy, not a sandbox.** Children inherit
|
|
63
63
|
`$HOME`, SSH/cloud credentials, network access, and the parent filesystem.
|
|
64
64
|
Git worktrees only isolate the checkout. For untrusted tasks, use an outer
|
|
65
65
|
container/cgroup/network policy.
|
|
66
|
-
|
|
66
|
+
11. **`max_cost` is accounting, not a hard provider gate.** Usage arrives after a
|
|
67
67
|
turn; orphans may spend money the ledger never sees. It caps provider-reported
|
|
68
68
|
execution cost only: TypeSafe reports routing tokens, not currency, so selector
|
|
69
69
|
cost is unreported and outside `max_cost`. Combine with provider account
|
|
@@ -81,12 +81,11 @@ and synthesis select from the new task instruction rather than the assembled
|
|
|
81
81
|
transcript. Task text and model descriptions are user content and can themselves
|
|
82
82
|
contain secrets; there is no guaranteed redaction.
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
`Authorization` header to the fixed official HTTPS endpoint, with redirects
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
conversation.
|
|
84
|
+
Version `0.10.0` reads the TypeSafe credential from `jevRouting.apiKey` in the private user-level `~/.pi/subagent.json`. This is plaintext storage: restrict file access and protect editor backups and synchronized copies. Same-user processes, including children with filesystem access, may read it. Profiles and worktrees do not protect this file from those processes.
|
|
85
|
+
|
|
86
|
+
The transport sends the key only as an `Authorization` header to the fixed official HTTPS endpoint, with redirects disabled. It does not automatically copy the key into prompts, selector JSON bodies, argv, child manifests, logs, receipts or results. Never serialize or log the complete routing configuration. Rotate any credential pasted into a transcript or shared in conversation.
|
|
87
|
+
|
|
88
|
+
Published npm `0.9.0` uses the older environment-based mechanism. In `0.10.0`, `apiKeyEnv` is rejected with manual migration guidance and no environment fallback. Unrelated `PI_SUBAGENT_*` runtime settings remain supported.
|
|
90
89
|
|
|
91
90
|
New extension-managed dispatch is Pi-only. A `backend: "codex"` or
|
|
92
91
|
`backend: "claude"` new task is rejected before any selector or provider work,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cr1ms0n/pi-subagent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Community fork of Luke Parke's pi-subagent with Jev model/tool routing and verified Pi child capabilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -10,6 +10,13 @@
|
|
|
10
10
|
"cr1ms0n (fork maintainer)"
|
|
11
11
|
],
|
|
12
12
|
"homepage": "https://www.npmjs.com/package/@cr1ms0n/pi-subagent",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/awoaCrim/pi-smart-subagents.git"
|
|
16
|
+
},
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/awoaCrim/pi-smart-subagents/issues"
|
|
19
|
+
},
|
|
13
20
|
"keywords": [
|
|
14
21
|
"pi-package",
|
|
15
22
|
"pi",
|
|
@@ -31,6 +38,7 @@
|
|
|
31
38
|
"skills",
|
|
32
39
|
"docs",
|
|
33
40
|
"README.md",
|
|
41
|
+
"README.zh-CN.md",
|
|
34
42
|
"CHANGELOG.md",
|
|
35
43
|
"LICENSE"
|
|
36
44
|
],
|
package/skills/subagent/SKILL.md
CHANGED
|
@@ -136,7 +136,11 @@ An optional candidate `thinking` value is an opaque Pi thinking-level string;
|
|
|
136
136
|
common values include `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and
|
|
137
137
|
`max`, but model-specific values are passed through unchanged. It is a default:
|
|
138
138
|
explicit task, agent, and profile `taskDefaults.thinking` values override it.
|
|
139
|
-
The extension re-reads `jevRouting` on each dispatch and injects
|
|
140
|
-
routing guidance into the parent prompt.
|
|
141
|
-
|
|
142
|
-
|
|
139
|
+
The extension re-reads `jevRouting` on each dispatch and injects non-secret
|
|
140
|
+
routing guidance into the parent prompt. The user stores the TypeSafe credential
|
|
141
|
+
in `jevRouting.apiKey` in the private `~/.pi/subagent.json`; do not read, display
|
|
142
|
+
or copy the key into task text, prompts or output. Legacy `apiKeyEnv` is rejected
|
|
143
|
+
with migration guidance; there is no environment fallback. If the config or key
|
|
144
|
+
is missing or invalid, management remains available but new spawns, `/btw`, plan,
|
|
145
|
+
resume, fork and synthesis are rejected. This config-file credential contract ships in
|
|
146
|
+
npm 0.10.0; published npm 0.9.0 uses the old environment mechanism.
|
package/src/config.ts
CHANGED
|
@@ -61,7 +61,7 @@ export interface SubagentConfig {
|
|
|
61
61
|
lockRetentionDays: number;
|
|
62
62
|
/** Legacy per-profile defaults. Model/fallback fields are retained for config compatibility but ignored by Jev routing. */
|
|
63
63
|
taskDefaults?: TaskDefaultsByProfile;
|
|
64
|
-
/**
|
|
64
|
+
/** Private Jev configuration including apiKey. Never log or serialize the whole config. */
|
|
65
65
|
jevRouting?: JevRoutingConfig;
|
|
66
66
|
/** Safe migration or parse failure; existing-run management remains available. */
|
|
67
67
|
jevRoutingError?: string;
|
package/src/jev-router.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Buffer } from "node:buffer";
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { Semaphore } from "./semaphore.js";
|
|
4
4
|
import { isThinkingLevel } from "./thinking.js";
|
|
5
|
+
import { normalizeRoutingApiKey } from "./routing-policy.js";
|
|
5
6
|
import {
|
|
6
7
|
DEFAULT_ROUTING_CONCURRENCY,
|
|
7
8
|
MAX_ROUTING_MODEL_ID_LENGTH,
|
|
@@ -58,7 +59,7 @@ export type {
|
|
|
58
59
|
* - A single logical deadline = min(config.timeoutMs, caller absolute deadline) spans every
|
|
59
60
|
* request and all limiter waiting. Concurrent HTTP requests are bounded to two by default.
|
|
60
61
|
* - Only `https://api.typesafe.ai/v1/systemone` with `redirect:"error"`; the Bearer key comes
|
|
61
|
-
* from the
|
|
62
|
+
* from the private config snapshot and is never copied into request bodies, results or messages.
|
|
62
63
|
* - Responses are untrusted data: shape, answer type, question set, allowed options, finite
|
|
63
64
|
* probabilities, probability sum tolerance, confidence, usage counts and selector version
|
|
64
65
|
* are validated. Valid low-confidence choices are accepted (no threshold, no substitution).
|
|
@@ -72,7 +73,7 @@ export type {
|
|
|
72
73
|
* - Failures return a discriminated result that includes every available receipt; there is no
|
|
73
74
|
* automatic selector retry, fallback or emergency model.
|
|
74
75
|
*
|
|
75
|
-
* Collaborators (`fetchImpl`, `
|
|
76
|
+
* Collaborators (`fetchImpl`, `now`, `idFactory`, `limiter`, `onReceipt`) are all
|
|
76
77
|
* injectable so the whole surface is testable offline with zero provider calls.
|
|
77
78
|
*/
|
|
78
79
|
|
|
@@ -82,12 +83,10 @@ export interface RoutingLimiter {
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
export interface JevRouterOptions {
|
|
85
|
-
/** Frozen per-invocation config snapshot. */
|
|
86
|
+
/** Frozen per-invocation config snapshot containing a private credential. Never log it. */
|
|
86
87
|
config: JevRoutingConfig;
|
|
87
88
|
/** Injected transport; defaults to global `fetch`. */
|
|
88
89
|
fetchImpl?: typeof fetch;
|
|
89
|
-
/** Injected environment accessor; defaults to `process.env`. */
|
|
90
|
-
env?: (name: string) => string | undefined;
|
|
91
90
|
/** Injected clock; defaults to `Date.now`. */
|
|
92
91
|
now?: () => number;
|
|
93
92
|
/** Injected unique-ID factory; defaults to `randomUUID`. */
|
|
@@ -411,7 +410,7 @@ function validateAnswers(body: unknown, questions: readonly QuestionSpec[]): Ans
|
|
|
411
410
|
|
|
412
411
|
function selectorStatusFailure(status: number): { code: RoutingFailureCode; message: string } {
|
|
413
412
|
if (status === 401 || status === 403) {
|
|
414
|
-
return { code: "unauthorized", message: "TypeSafe rejected the routing credential (HTTP 401/403). Check
|
|
413
|
+
return { code: "unauthorized", message: "TypeSafe rejected the routing credential (HTTP 401/403). Check jevRouting.apiKey in your private ~/.pi/subagent.json configuration." };
|
|
415
414
|
}
|
|
416
415
|
if (status === 422) {
|
|
417
416
|
return { code: "invalid_request", message: "TypeSafe rejected the routing request as invalid (HTTP 422). Check selectorModel and the configured candidate/tool descriptions." };
|
|
@@ -607,7 +606,6 @@ function freezeReceipt(draft: ReceiptDraft): RoutingReceipt {
|
|
|
607
606
|
export class JevRouter {
|
|
608
607
|
private readonly config: JevRoutingConfig;
|
|
609
608
|
private readonly fetchImpl: typeof fetch | undefined;
|
|
610
|
-
private readonly env: (name: string) => string | undefined;
|
|
611
609
|
private readonly now: () => number;
|
|
612
610
|
private readonly idFactory: () => string;
|
|
613
611
|
private readonly limiter: RoutingLimiter;
|
|
@@ -616,7 +614,6 @@ export class JevRouter {
|
|
|
616
614
|
constructor(options: JevRouterOptions) {
|
|
617
615
|
this.config = options.config;
|
|
618
616
|
this.fetchImpl = options.fetchImpl ?? (typeof globalThis.fetch === "function" ? globalThis.fetch : undefined);
|
|
619
|
-
this.env = options.env ?? ((name) => process.env[name]);
|
|
620
617
|
this.now = options.now ?? Date.now;
|
|
621
618
|
this.idFactory = options.idFactory ?? randomUUID;
|
|
622
619
|
this.limiter = options.limiter ?? sharedLimiter;
|
|
@@ -647,12 +644,11 @@ export class JevRouter {
|
|
|
647
644
|
return this.fail("too_many_tools", `At most ${MAX_ROUTING_TOOL_QUESTIONS} eligible tools can be considered in one selection.`, call);
|
|
648
645
|
}
|
|
649
646
|
|
|
650
|
-
const
|
|
651
|
-
const apiKey = typeof rawKey === "string" ? rawKey.trim() : "";
|
|
647
|
+
const apiKey = normalizeRoutingApiKey(this.config.apiKey);
|
|
652
648
|
if (!apiKey) {
|
|
653
649
|
return this.fail(
|
|
654
650
|
"missing_api_key",
|
|
655
|
-
|
|
651
|
+
"The TypeSafe routing credential is missing or invalid: set jevRouting.apiKey in your private ~/.pi/subagent.json to a non-blank key without embedded whitespace or control characters, then retry the dispatch.",
|
|
656
652
|
call,
|
|
657
653
|
);
|
|
658
654
|
}
|
package/src/routing-policy.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { isThinkingLevel } from "./thinking.js";
|
|
2
2
|
import {
|
|
3
|
-
DEFAULT_API_KEY_ENV,
|
|
4
3
|
DEFAULT_ROUTING_TIMEOUT_MS,
|
|
5
4
|
DEFAULT_SELECTOR_MODEL,
|
|
6
5
|
MAX_ROUTING_MODEL_ID_LENGTH,
|
|
@@ -30,22 +29,21 @@ export type {
|
|
|
30
29
|
* - `parseJevRouting(raw, source?)` parses the `jevRouting` **subtree** (not the whole
|
|
31
30
|
* `~/.pi/subagent.json` file) and returns an immutable snapshot. It throws a plain
|
|
32
31
|
* `Error` with an actionable message on any unknown field, duplicate/blank model ID,
|
|
33
|
-
* blank description, invalid
|
|
34
|
-
* or unsupported candidate count. Callers (`src/config.ts`)
|
|
35
|
-
*
|
|
32
|
+
* blank description, missing/invalid credential, non-integer/out-of-range timeout
|
|
33
|
+
* or unsupported candidate count. Callers (`src/config.ts`) expose safe error messages;
|
|
34
|
+
* the parser never reads the environment or a provider catalog.
|
|
36
35
|
* - `formatJevRoutingPrompt` renders model-facing guidance and works with **no** config, no
|
|
37
36
|
* credential and no inference, so management actions stay independent of routing setup.
|
|
38
37
|
* - The candidate helpers are pure; they never contact Pi or TypeSafe.
|
|
39
38
|
*
|
|
40
|
-
* The
|
|
41
|
-
*
|
|
42
|
-
* `
|
|
39
|
+
* The private snapshot contains the credential from `apiKey`. Never serialize it into
|
|
40
|
+
* model-facing guidance or selector input. Credential diagnostics contain no supplied
|
|
41
|
+
* values. Legacy `apiKeyEnv` is rejected with manual migration guidance, without lookup.
|
|
43
42
|
*/
|
|
44
43
|
|
|
45
44
|
/** Default config-file label used in prose (owned by `config.ts` for file reads). */
|
|
46
45
|
export const JEV_ROUTING_CONFIG_FILE = "~/.pi/subagent.json";
|
|
47
46
|
|
|
48
|
-
const ENV_VAR_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
49
47
|
/** Exact provider/model ID: at least one slash, further ID slashes allowed; no whitespace, control chars or globs. */
|
|
50
48
|
const MODEL_ID = /^[^\s\u0000-\u001f\u007f/*?]+(?:\/[^\s\u0000-\u001f\u007f/*?]+)+$/u;
|
|
51
49
|
const MAX_GUIDANCE_MODEL_LINES = 50;
|
|
@@ -76,12 +74,19 @@ function parseSelectorModel(value: unknown, source: string): string {
|
|
|
76
74
|
return model;
|
|
77
75
|
}
|
|
78
76
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (typeof value !== "string"
|
|
82
|
-
|
|
77
|
+
/** Shared credential normalization for config parsing and defensive transport validation. */
|
|
78
|
+
export function normalizeRoutingApiKey(value: unknown): string | undefined {
|
|
79
|
+
if (typeof value !== "string") return undefined;
|
|
80
|
+
const key = value.trim();
|
|
81
|
+
return key && !/[\s\u0000-\u001f\u007f-\u009f]/u.test(key) ? key : undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function parseApiKey(value: unknown, source: string): string {
|
|
85
|
+
const key = normalizeRoutingApiKey(value);
|
|
86
|
+
if (key === undefined) {
|
|
87
|
+
invalid(source, "apiKey is required and must be a non-blank key without embedded whitespace or control characters; store it only in your private user config");
|
|
83
88
|
}
|
|
84
|
-
return
|
|
89
|
+
return key;
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
function parseTimeoutMs(value: unknown, source: string): number {
|
|
@@ -144,11 +149,14 @@ function parseModels(value: unknown, source: string): readonly JevRoutingModelEn
|
|
|
144
149
|
*/
|
|
145
150
|
export function parseJevRouting(raw: unknown, source = JEV_ROUTING_CONFIG_FILE): JevRoutingConfig {
|
|
146
151
|
const record = requireObject(raw, source, "jevRouting");
|
|
147
|
-
|
|
152
|
+
if (Object.prototype.hasOwnProperty.call(record, "apiKeyEnv")) {
|
|
153
|
+
invalid(source, "apiKeyEnv is no longer supported; remove it and set jevRouting.apiKey to the credential in your private user config. No environment fallback or automatic migration is performed");
|
|
154
|
+
}
|
|
155
|
+
rejectUnknownKeys(record, ["selectorModel", "apiKey", "timeoutMs", "models"], source, "jevRouting");
|
|
148
156
|
|
|
149
157
|
const snapshot: JevRoutingConfig = {
|
|
150
158
|
selectorModel: parseSelectorModel(record.selectorModel, source),
|
|
151
|
-
|
|
159
|
+
apiKey: parseApiKey(record.apiKey, source),
|
|
152
160
|
timeoutMs: parseTimeoutMs(record.timeoutMs, source),
|
|
153
161
|
models: parseModels(record.models, source),
|
|
154
162
|
};
|
|
@@ -160,7 +168,7 @@ export function jevRoutingTemplate(): string {
|
|
|
160
168
|
return JSON.stringify({
|
|
161
169
|
jevRouting: {
|
|
162
170
|
selectorModel: DEFAULT_SELECTOR_MODEL,
|
|
163
|
-
|
|
171
|
+
apiKey: "<your-typesafe-api-key>",
|
|
164
172
|
timeoutMs: DEFAULT_ROUTING_TIMEOUT_MS,
|
|
165
173
|
models: [
|
|
166
174
|
{
|
|
@@ -230,7 +238,7 @@ function routingSummary(config: JevRoutingConfig): string[] {
|
|
|
230
238
|
"Every new task/tasks[] spawn, action:\"plan\" request, /btw, resume, fork and synthesis is routed by the Jev selector against the user's candidate-model list.",
|
|
231
239
|
"Do not pass model or fallback_models: those fields no longer select a route on new work and are rejected. Management actions (status/wait/cancel/steer/diff/apply/discard) never call the selector and need no credential.",
|
|
232
240
|
`Selector: ${config.selectorModel} (pin an exact version instead of the moving alias to make selection reproducible).`,
|
|
233
|
-
|
|
241
|
+
"Credential: jevRouting.apiKey in the private ~/.pi/subagent.json config file. Never read or copy its value into prompts, logs or results; the routing transport uses it only for the Authorization header.",
|
|
234
242
|
`Logical selection deadline: ${config.timeoutMs} ms, covering all selector requests and waiting for one invocation.`,
|
|
235
243
|
"Only Pi-backed new dispatch is supported; native Codex/Claude new dispatches are rejected rather than routed.",
|
|
236
244
|
"Candidate models (exact IDs; the user's per-model characteristics are the matching criteria):",
|
|
@@ -259,7 +267,7 @@ export function formatJevRoutingPrompt(config: JevRoutingConfig | undefined, err
|
|
|
259
267
|
"## Subagent routing (Jev / TypeSafe)",
|
|
260
268
|
error || `No valid jevRouting configuration was found in ${JEV_ROUTING_CONFIG_FILE}.`,
|
|
261
269
|
"Management actions (status/wait/cancel/steer/diff/apply/discard) remain available, but every new task/tasks[] spawn, plan, /btw, resume, fork and synthesis is rejected until jevRouting is configured.",
|
|
262
|
-
"Add jevRouting with selectorModel,
|
|
270
|
+
"Add jevRouting with selectorModel, apiKey and 1-255 candidate model entries (exact provider/model IDs plus user-written characteristics, including Chinese). The user must store the credential in the private config file, not in chat or source control. Do not read or display the key. Legacy apiKeyEnv is rejected; there is no environment fallback.",
|
|
263
271
|
"Do not pass model or fallback_models; the selector chooses the execution model and tools.",
|
|
264
272
|
"Use the package routing template; do not invent model IDs or import legacy modelPolicy entries automatically.",
|
|
265
273
|
].join("\n");
|
package/src/routing-types.ts
CHANGED
|
@@ -18,8 +18,6 @@ export const TYPESAFE_SYSTEMONE_ENDPOINT = "https://api.typesafe.ai/v1/systemone
|
|
|
18
18
|
|
|
19
19
|
/** Stable alias default. An exact supported version may be pinned through config. */
|
|
20
20
|
export const DEFAULT_SELECTOR_MODEL = "jev-latest";
|
|
21
|
-
/** Environment variable that holds the TypeSafe credential. Never the credential value. */
|
|
22
|
-
export const DEFAULT_API_KEY_ENV = "TYPESAFE_API_KEY";
|
|
23
21
|
/** Default logical selection deadline in milliseconds. */
|
|
24
22
|
export const DEFAULT_ROUTING_TIMEOUT_MS = 15_000;
|
|
25
23
|
export const ROUTING_TIMEOUT_MIN_MS = 100;
|
|
@@ -86,11 +84,13 @@ export interface JevRoutingModelEntry {
|
|
|
86
84
|
|
|
87
85
|
/**
|
|
88
86
|
* Immutable `jevRouting` snapshot parsed from the user config subtree.
|
|
89
|
-
*
|
|
87
|
+
* Contains a private credential. Never log this snapshot or serialize it into prompts,
|
|
88
|
+
* selector bodies, task specs, receipts or results; use explicit non-secret projections.
|
|
90
89
|
*/
|
|
91
90
|
export interface JevRoutingConfig {
|
|
92
91
|
readonly selectorModel: string;
|
|
93
|
-
|
|
92
|
+
/** User-configured TypeSafe credential; transport Authorization header only. */
|
|
93
|
+
readonly apiKey: string;
|
|
94
94
|
readonly timeoutMs: number;
|
|
95
95
|
readonly models: readonly JevRoutingModelEntry[];
|
|
96
96
|
}
|