@peakflo/agent-harness 0.0.0-stage → 0.2.2-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +68 -2
- package/dist/adapters/acp-adapter.d.ts +177 -0
- package/dist/adapters/acp-adapter.js +1732 -0
- package/dist/adapters/claude-code-adapter.d.ts +171 -0
- package/dist/adapters/claude-code-adapter.js +1918 -0
- package/dist/adapters/codex-adapter.d.ts +67 -0
- package/dist/adapters/codex-adapter.js +681 -0
- package/dist/adapters/codex-app-server-adapter.d.ts +105 -0
- package/dist/adapters/codex-app-server-adapter.js +1309 -0
- package/dist/adapters/coding-agent-adapter.d.ts +289 -0
- package/dist/adapters/coding-agent-adapter.js +31 -0
- package/dist/adapters/index.d.ts +17 -0
- package/dist/adapters/index.js +35 -0
- package/dist/adapters/model-gateway.d.ts +84 -0
- package/dist/adapters/model-gateway.js +270 -0
- package/dist/adapters/opencode-adapter.d.ts +403 -0
- package/dist/adapters/opencode-adapter.js +2710 -0
- package/dist/adapters/opencode-config.d.ts +41 -0
- package/dist/adapters/opencode-config.js +147 -0
- package/dist/adapters/opencode-server-policy.d.ts +82 -0
- package/dist/adapters/opencode-server-policy.js +148 -0
- package/dist/adapters/pi-adapter.d.ts +168 -0
- package/dist/adapters/pi-adapter.js +1468 -0
- package/dist/capabilities.d.ts +58 -0
- package/dist/capabilities.js +161 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +231 -0
- package/dist/contract.d.ts +273 -0
- package/dist/contract.js +357 -0
- package/dist/control-plane.d.ts +178 -0
- package/dist/control-plane.js +431 -0
- package/dist/credential-store.d.ts +110 -0
- package/dist/credential-store.js +181 -0
- package/dist/enrol/browser-enrol.d.ts +85 -0
- package/dist/enrol/browser-enrol.js +387 -0
- package/dist/github-command-guards.d.ts +20 -0
- package/dist/github-command-guards.js +330 -0
- package/dist/github-proxy-certificates.d.ts +25 -0
- package/dist/github-proxy-certificates.js +145 -0
- package/dist/github-proxy.d.ts +37 -0
- package/dist/github-proxy.js +476 -0
- package/dist/github-workspace.d.ts +55 -0
- package/dist/github-workspace.js +537 -0
- package/dist/harness.d.ts +333 -0
- package/dist/harness.js +1414 -0
- package/dist/health.d.ts +45 -0
- package/dist/health.js +116 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +123 -0
- package/dist/model-probe.d.ts +96 -0
- package/dist/model-probe.js +195 -0
- package/dist/options.d.ts +15 -0
- package/dist/options.js +212 -0
- package/dist/ports-default.d.ts +212 -0
- package/dist/ports-default.js +333 -0
- package/dist/ports.d.ts +436 -0
- package/dist/ports.js +27 -0
- package/dist/runtime/attachments.d.ts +92 -0
- package/dist/runtime/attachments.js +377 -0
- package/dist/runtime/child-stream-guards.d.ts +45 -0
- package/dist/runtime/child-stream-guards.js +111 -0
- package/dist/runtime/conversation.d.ts +87 -0
- package/dist/runtime/conversation.js +156 -0
- package/dist/runtime/lease-renewal.d.ts +169 -0
- package/dist/runtime/lease-renewal.js +433 -0
- package/dist/runtime/mcp-auth-proxy.d.ts +82 -0
- package/dist/runtime/mcp-auth-proxy.js +300 -0
- package/dist/runtime/permission-mode.d.ts +116 -0
- package/dist/runtime/permission-mode.js +146 -0
- package/dist/runtime/projection.d.ts +71 -0
- package/dist/runtime/projection.js +211 -0
- package/dist/runtime/session-runner.d.ts +520 -0
- package/dist/runtime/session-runner.js +2122 -0
- package/dist/shared/reasoning-effort.d.ts +11 -0
- package/dist/shared/reasoning-effort.js +20 -0
- package/dist/store/conversation-store.d.ts +100 -0
- package/dist/store/conversation-store.js +305 -0
- package/dist/store/sqlite-store.d.ts +120 -0
- package/dist/store/sqlite-store.js +346 -0
- package/dist/types.d.ts +952 -0
- package/dist/types.js +55 -0
- package/dist/version.d.ts +32 -0
- package/dist/version.js +77 -0
- package/package.json +53 -4
package/dist/health.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { HealthReport } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Health check for pf-bootstrap and for the Daytona liveness probe.
|
|
4
|
+
*
|
|
5
|
+
* SECURITY: bound to 127.0.0.1 ONLY. Nothing about the harness is reachable
|
|
6
|
+
* from outside the sandbox, and session users get no useful secret from it —
|
|
7
|
+
* the report deliberately carries no token.
|
|
8
|
+
*
|
|
9
|
+
* STOP MUST ACTUALLY CLOSE, and this is the reason `boundPort` and the socket
|
|
10
|
+
* tracking below exist. `server.close()` stops ACCEPTING connections but leaves
|
|
11
|
+
* any keep-alive connection open, and an open connection keeps the Node event
|
|
12
|
+
* loop alive — the exact class of bug Phase 0 hit, where a refused registration
|
|
13
|
+
* hung instead of exiting because a listening socket held the loop. `stop()`
|
|
14
|
+
* therefore destroys every tracked connection as well as closing the listener,
|
|
15
|
+
* so the process can end deliberately rather than only when a client happens to
|
|
16
|
+
* hang up.
|
|
17
|
+
*/
|
|
18
|
+
export declare class HealthServer {
|
|
19
|
+
private readonly port;
|
|
20
|
+
private readonly report;
|
|
21
|
+
private server;
|
|
22
|
+
/**
|
|
23
|
+
* The port the server actually bound to, RETAINED after `stop()`.
|
|
24
|
+
*
|
|
25
|
+
* With `port: 0` the OS chooses a free port, and a test — or an operator —
|
|
26
|
+
* needs to know which one to prove the socket is closed afterwards. Keeping it
|
|
27
|
+
* separate from `server` means it survives teardown.
|
|
28
|
+
*/
|
|
29
|
+
private bound;
|
|
30
|
+
/** Live connections, tracked so `stop()` can destroy them. */
|
|
31
|
+
private readonly sockets;
|
|
32
|
+
constructor(port: number, report: () => HealthReport);
|
|
33
|
+
/** The actual bound port, or null before `start()`. Survives `stop()`. */
|
|
34
|
+
get boundPort(): number | null;
|
|
35
|
+
start(): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* A Daytona resume can briefly have two bootstraps: the snapshot entrypoint
|
|
38
|
+
* waking with the VM and the control plane's fresh-token push. The stale
|
|
39
|
+
* process may bind 7777 before its registration is refused. Retrying this
|
|
40
|
+
* one loopback bind lets the useful process take over as soon as that stale
|
|
41
|
+
* process exits, without weakening enrolment or running two ready harnesses.
|
|
42
|
+
*/
|
|
43
|
+
private listenOnce;
|
|
44
|
+
stop(): Promise<void>;
|
|
45
|
+
}
|
package/dist/health.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HealthServer = void 0;
|
|
4
|
+
const node_http_1 = require("node:http");
|
|
5
|
+
const BIND_RETRY_INTERVAL_MS = 100;
|
|
6
|
+
const BIND_RETRY_TIMEOUT_MS = 10_000;
|
|
7
|
+
/**
|
|
8
|
+
* Health check for pf-bootstrap and for the Daytona liveness probe.
|
|
9
|
+
*
|
|
10
|
+
* SECURITY: bound to 127.0.0.1 ONLY. Nothing about the harness is reachable
|
|
11
|
+
* from outside the sandbox, and session users get no useful secret from it —
|
|
12
|
+
* the report deliberately carries no token.
|
|
13
|
+
*
|
|
14
|
+
* STOP MUST ACTUALLY CLOSE, and this is the reason `boundPort` and the socket
|
|
15
|
+
* tracking below exist. `server.close()` stops ACCEPTING connections but leaves
|
|
16
|
+
* any keep-alive connection open, and an open connection keeps the Node event
|
|
17
|
+
* loop alive — the exact class of bug Phase 0 hit, where a refused registration
|
|
18
|
+
* hung instead of exiting because a listening socket held the loop. `stop()`
|
|
19
|
+
* therefore destroys every tracked connection as well as closing the listener,
|
|
20
|
+
* so the process can end deliberately rather than only when a client happens to
|
|
21
|
+
* hang up.
|
|
22
|
+
*/
|
|
23
|
+
class HealthServer {
|
|
24
|
+
port;
|
|
25
|
+
report;
|
|
26
|
+
server = null;
|
|
27
|
+
/**
|
|
28
|
+
* The port the server actually bound to, RETAINED after `stop()`.
|
|
29
|
+
*
|
|
30
|
+
* With `port: 0` the OS chooses a free port, and a test — or an operator —
|
|
31
|
+
* needs to know which one to prove the socket is closed afterwards. Keeping it
|
|
32
|
+
* separate from `server` means it survives teardown.
|
|
33
|
+
*/
|
|
34
|
+
bound = null;
|
|
35
|
+
/** Live connections, tracked so `stop()` can destroy them. */
|
|
36
|
+
sockets = new Set();
|
|
37
|
+
constructor(port, report) {
|
|
38
|
+
this.port = port;
|
|
39
|
+
this.report = report;
|
|
40
|
+
}
|
|
41
|
+
/** The actual bound port, or null before `start()`. Survives `stop()`. */
|
|
42
|
+
get boundPort() {
|
|
43
|
+
return this.bound;
|
|
44
|
+
}
|
|
45
|
+
async start() {
|
|
46
|
+
const deadline = Date.now() + BIND_RETRY_TIMEOUT_MS;
|
|
47
|
+
for (;;) {
|
|
48
|
+
try {
|
|
49
|
+
await this.listenOnce();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (error?.code !== 'EADDRINUSE' ||
|
|
54
|
+
Date.now() >= deadline) {
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
await new Promise((resolve) => setTimeout(resolve, BIND_RETRY_INTERVAL_MS));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* A Daytona resume can briefly have two bootstraps: the snapshot entrypoint
|
|
63
|
+
* waking with the VM and the control plane's fresh-token push. The stale
|
|
64
|
+
* process may bind 7777 before its registration is refused. Retrying this
|
|
65
|
+
* one loopback bind lets the useful process take over as soon as that stale
|
|
66
|
+
* process exits, without weakening enrolment or running two ready harnesses.
|
|
67
|
+
*/
|
|
68
|
+
async listenOnce() {
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
const server = (0, node_http_1.createServer)((req, res) => {
|
|
71
|
+
if (req.url !== '/health' && req.url !== '/healthz') {
|
|
72
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
73
|
+
res.end(JSON.stringify({ error: 'Not found' }));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const body = this.report();
|
|
77
|
+
// 'starting' is not yet healthy, so a probe waits rather than
|
|
78
|
+
// declaring the sandbox ready too early.
|
|
79
|
+
const code = body.status === 'ready' ? 200 : 503;
|
|
80
|
+
res.writeHead(code, { 'Content-Type': 'application/json' });
|
|
81
|
+
res.end(JSON.stringify(body));
|
|
82
|
+
});
|
|
83
|
+
// Track every connection so teardown can force it closed. A default
|
|
84
|
+
// keep-alive client would otherwise keep the listener's underlying handle
|
|
85
|
+
// referenced past `close()`.
|
|
86
|
+
server.on('connection', (socket) => {
|
|
87
|
+
this.sockets.add(socket);
|
|
88
|
+
socket.on('close', () => this.sockets.delete(socket));
|
|
89
|
+
});
|
|
90
|
+
server.on('error', reject);
|
|
91
|
+
server.listen(this.port, '127.0.0.1', () => {
|
|
92
|
+
this.server = server;
|
|
93
|
+
const address = server.address();
|
|
94
|
+
this.bound = address ? address.port : this.port;
|
|
95
|
+
resolve();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
async stop() {
|
|
100
|
+
const server = this.server;
|
|
101
|
+
if (!server) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
this.server = null;
|
|
105
|
+
// Destroy in-flight connections FIRST, so `close()`'s callback is not left
|
|
106
|
+
// waiting on a keep-alive socket that never hangs up — which is precisely
|
|
107
|
+
// how a "stopped" harness stayed reachable and kept the event loop alive.
|
|
108
|
+
for (const socket of this.sockets) {
|
|
109
|
+
socket.destroy();
|
|
110
|
+
}
|
|
111
|
+
this.sockets.clear();
|
|
112
|
+
server.closeAllConnections?.();
|
|
113
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
exports.HealthServer = HealthServer;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @peakflo/agent-harness — the headless Peakflo coding-agent runtime.
|
|
3
|
+
*
|
|
4
|
+
* ONE CODEBASE, THREE HOSTING MODES: a Daytona cloud sandbox, a customer's own
|
|
5
|
+
* VM, and the 20x desktop. Nothing below asks which one it is running in. Where
|
|
6
|
+
* a host genuinely differs it supplies a PORT (see ./ports), and every port
|
|
7
|
+
* describes a CAPABILITY rather than an identity — the moment the runtime can
|
|
8
|
+
* ask "am I the desktop?", the three modes stop being the same program.
|
|
9
|
+
*/
|
|
10
|
+
export { Harness, UpgradeRequestedError, VersionRefusedError } from './harness';
|
|
11
|
+
export type { HarnessDeps, HarnessUpgradeRequest } from './harness';
|
|
12
|
+
export { ControlPlaneClient, ControlPlaneError, ProtocolRefusedError } from './control-plane';
|
|
13
|
+
export type { ControlPlaneClientOptions, HeartbeatInput, RegisterInput } from './control-plane';
|
|
14
|
+
export { HealthServer } from './health';
|
|
15
|
+
export { OptionsError, parseOptions } from './options';
|
|
16
|
+
export { getHarnessVersion, getPiVersion } from './version';
|
|
17
|
+
export { probe, probeCapabilities } from './capabilities';
|
|
18
|
+
export type { ProbeCapabilitiesOptions, ProbeResult } from './capabilities';
|
|
19
|
+
/**
|
|
20
|
+
* Model catalogue probing — the harness half of the dynamic model picker.
|
|
21
|
+
* `flattenProviderModels` mirrors 20x's AgentForm flattening (array OR object
|
|
22
|
+
* map of `provider.models`); `probeModelCatalog` reads every installed
|
|
23
|
+
* backend's list into a per-backend + flat catalogue.
|
|
24
|
+
*/
|
|
25
|
+
export { flattenProviderModels, probeBackendModels, probeModelCatalog } from './model-probe';
|
|
26
|
+
export type { ModelCatalog, ModelProbeAdapterFactory, ProviderRecord } from './model-probe';
|
|
27
|
+
export type { ArtifactInspector, EventSink, HarnessPorts, InspectedArtifact, Notifier, SecretProvider, StoredPart, StoredPartInput, TaskMcpProvider, TaskMcpScope, Telemetry, TokenInjector, TranscriptStore, WorkspaceProvider, WorkspaceRequest } from './ports';
|
|
28
|
+
export { CompositeEventSink, DirectoryWorkspaceProvider, NoArtifactInspector, NoSecretProvider, NoopTelemetry, NullEventSink, ServerNotifier, SessionTokenInjector, StaticTaskMcpProvider } from './ports-default';
|
|
29
|
+
export type { DirectoryWorkspaceOptions } from './ports-default';
|
|
30
|
+
export { GitHubWorkspaceProvider } from './github-workspace';
|
|
31
|
+
export type { GitHubWorkspaceOptions } from './github-workspace';
|
|
32
|
+
export { McpAuthProxy } from './runtime/mcp-auth-proxy';
|
|
33
|
+
export type { McpAuthProxyOptions } from './runtime/mcp-auth-proxy';
|
|
34
|
+
export { SessionRunner } from './runtime/session-runner';
|
|
35
|
+
export type { RunTurnResult, SessionRunnerDeps } from './runtime/session-runner';
|
|
36
|
+
export { projectParts, toIngestPart } from './runtime/projection';
|
|
37
|
+
export type { ProjectableMessagePart } from './runtime/projection';
|
|
38
|
+
export { guardChildStreams, guardStream, isBenignStreamError, writeToChildStdin } from './runtime/child-stream-guards';
|
|
39
|
+
export { MemoryTranscriptStore, SqliteTranscriptStore, openTranscriptStore } from './store/sqlite-store';
|
|
40
|
+
export type { OpenStoreResult } from './store/sqlite-store';
|
|
41
|
+
/**
|
|
42
|
+
* Extracted from 20x UNCHANGED — the whole `src/main/adapters/` directory had
|
|
43
|
+
* zero Electron imports, which is why the measurement put the extraction at two
|
|
44
|
+
* weeks rather than two months.
|
|
45
|
+
*/
|
|
46
|
+
export * from './adapters';
|
|
47
|
+
/**
|
|
48
|
+
* The control plane imports these too. The predicates are pure and SHARED on
|
|
49
|
+
* purpose, so the two sides cannot disagree about what "supported", "final" or
|
|
50
|
+
* "ephemeral" means. A rule written twice is a rule that drifts.
|
|
51
|
+
*/
|
|
52
|
+
export { CONTRACT_ERROR_CODES, DEFAULT_BASE_DELAY_MS, DEFAULT_HEARTBEAT_INTERVAL_MS, DEFAULT_LEASE_TTL_MS, DEFAULT_LEASE_WAIT_SECONDS, DEFAULT_MAX_ATTEMPTS, EPHEMERAL_PART_TYPES, FINAL_HTTP_STATUSES, HARNESS_UPGRADE_EXIT_CODE, MAX_INGEST_PARTS, MAX_INGEST_PART_BYTES, MAX_LEASE_WAIT_SECONDS, MIN_SUPPORTED_PROTOCOL_VERSION, MISSED_HEARTBEATS_BEFORE_OFFLINE, PERSISTED_PART_TYPES, PROTOCOL_VERSION, PROTOCOL_VERSION_HEADER, ROUTES, ingestPartByteLength, isEphemeralPartType, isFinalStatus, isPersistablePart, isProtocolSupported } from './contract';
|
|
53
|
+
export type { BootstrapRequest, BootstrapResponse, ControlPlaneErrorBody, CredentialRotateRequest, CredentialRotateResponse, DeregisterRequest, DeregisterResponse, HarnessOptions, HealthReport, IngestRejection, IngestRejectionReason, IngestRequest, IngestResponse, JsonValue, LeaseRenewRequest, LeaseRenewResponse, LeaseResponse, LeasedAgent, LeasedAttachment, LeasedLimits, LeasedMcpServer, LeasedModel, LeasedSession, LeasedSkill, LeasedTurnInput, LeasedWorkspace, LeasedGitHubCredential, LeasedGitHubRepository, ProtocolEnvelope, RunnerBackend, RunnerCapabilities, RunnerHeartbeatRequest, RunnerHeartbeatResponse, RunnerModel, RunnerRegisterRequest, RunnerRegisterResponse, RunnerRuntime, RunnerStatus, SessionCommand, TranscriptPart, TranscriptPartInput, TurnMetadata } from './types';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @peakflo/agent-harness — the headless Peakflo coding-agent runtime.
|
|
4
|
+
*
|
|
5
|
+
* ONE CODEBASE, THREE HOSTING MODES: a Daytona cloud sandbox, a customer's own
|
|
6
|
+
* VM, and the 20x desktop. Nothing below asks which one it is running in. Where
|
|
7
|
+
* a host genuinely differs it supplies a PORT (see ./ports), and every port
|
|
8
|
+
* describes a CAPABILITY rather than an identity — the moment the runtime can
|
|
9
|
+
* ask "am I the desktop?", the three modes stop being the same program.
|
|
10
|
+
*/
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
23
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.MIN_SUPPORTED_PROTOCOL_VERSION = exports.MAX_LEASE_WAIT_SECONDS = exports.MAX_INGEST_PART_BYTES = exports.MAX_INGEST_PARTS = exports.HARNESS_UPGRADE_EXIT_CODE = exports.FINAL_HTTP_STATUSES = exports.EPHEMERAL_PART_TYPES = exports.DEFAULT_MAX_ATTEMPTS = exports.DEFAULT_LEASE_WAIT_SECONDS = exports.DEFAULT_LEASE_TTL_MS = exports.DEFAULT_HEARTBEAT_INTERVAL_MS = exports.DEFAULT_BASE_DELAY_MS = exports.CONTRACT_ERROR_CODES = exports.openTranscriptStore = exports.SqliteTranscriptStore = exports.MemoryTranscriptStore = exports.writeToChildStdin = exports.isBenignStreamError = exports.guardStream = exports.guardChildStreams = exports.toIngestPart = exports.projectParts = exports.SessionRunner = exports.McpAuthProxy = exports.GitHubWorkspaceProvider = exports.StaticTaskMcpProvider = exports.SessionTokenInjector = exports.ServerNotifier = exports.NullEventSink = exports.NoopTelemetry = exports.NoSecretProvider = exports.NoArtifactInspector = exports.DirectoryWorkspaceProvider = exports.CompositeEventSink = exports.probeModelCatalog = exports.probeBackendModels = exports.flattenProviderModels = exports.probeCapabilities = exports.probe = exports.getPiVersion = exports.getHarnessVersion = exports.parseOptions = exports.OptionsError = exports.HealthServer = exports.ProtocolRefusedError = exports.ControlPlaneError = exports.ControlPlaneClient = exports.VersionRefusedError = exports.UpgradeRequestedError = exports.Harness = void 0;
|
|
27
|
+
exports.isProtocolSupported = exports.isPersistablePart = exports.isFinalStatus = exports.isEphemeralPartType = exports.ingestPartByteLength = exports.ROUTES = exports.PROTOCOL_VERSION_HEADER = exports.PROTOCOL_VERSION = exports.PERSISTED_PART_TYPES = exports.MISSED_HEARTBEATS_BEFORE_OFFLINE = void 0;
|
|
28
|
+
/* ---- The process ---------------------------------------------------------- */
|
|
29
|
+
var harness_1 = require("./harness");
|
|
30
|
+
Object.defineProperty(exports, "Harness", { enumerable: true, get: function () { return harness_1.Harness; } });
|
|
31
|
+
Object.defineProperty(exports, "UpgradeRequestedError", { enumerable: true, get: function () { return harness_1.UpgradeRequestedError; } });
|
|
32
|
+
Object.defineProperty(exports, "VersionRefusedError", { enumerable: true, get: function () { return harness_1.VersionRefusedError; } });
|
|
33
|
+
var control_plane_1 = require("./control-plane");
|
|
34
|
+
Object.defineProperty(exports, "ControlPlaneClient", { enumerable: true, get: function () { return control_plane_1.ControlPlaneClient; } });
|
|
35
|
+
Object.defineProperty(exports, "ControlPlaneError", { enumerable: true, get: function () { return control_plane_1.ControlPlaneError; } });
|
|
36
|
+
Object.defineProperty(exports, "ProtocolRefusedError", { enumerable: true, get: function () { return control_plane_1.ProtocolRefusedError; } });
|
|
37
|
+
var health_1 = require("./health");
|
|
38
|
+
Object.defineProperty(exports, "HealthServer", { enumerable: true, get: function () { return health_1.HealthServer; } });
|
|
39
|
+
var options_1 = require("./options");
|
|
40
|
+
Object.defineProperty(exports, "OptionsError", { enumerable: true, get: function () { return options_1.OptionsError; } });
|
|
41
|
+
Object.defineProperty(exports, "parseOptions", { enumerable: true, get: function () { return options_1.parseOptions; } });
|
|
42
|
+
var version_1 = require("./version");
|
|
43
|
+
Object.defineProperty(exports, "getHarnessVersion", { enumerable: true, get: function () { return version_1.getHarnessVersion; } });
|
|
44
|
+
Object.defineProperty(exports, "getPiVersion", { enumerable: true, get: function () { return version_1.getPiVersion; } });
|
|
45
|
+
var capabilities_1 = require("./capabilities");
|
|
46
|
+
Object.defineProperty(exports, "probe", { enumerable: true, get: function () { return capabilities_1.probe; } });
|
|
47
|
+
Object.defineProperty(exports, "probeCapabilities", { enumerable: true, get: function () { return capabilities_1.probeCapabilities; } });
|
|
48
|
+
/**
|
|
49
|
+
* Model catalogue probing — the harness half of the dynamic model picker.
|
|
50
|
+
* `flattenProviderModels` mirrors 20x's AgentForm flattening (array OR object
|
|
51
|
+
* map of `provider.models`); `probeModelCatalog` reads every installed
|
|
52
|
+
* backend's list into a per-backend + flat catalogue.
|
|
53
|
+
*/
|
|
54
|
+
var model_probe_1 = require("./model-probe");
|
|
55
|
+
Object.defineProperty(exports, "flattenProviderModels", { enumerable: true, get: function () { return model_probe_1.flattenProviderModels; } });
|
|
56
|
+
Object.defineProperty(exports, "probeBackendModels", { enumerable: true, get: function () { return model_probe_1.probeBackendModels; } });
|
|
57
|
+
Object.defineProperty(exports, "probeModelCatalog", { enumerable: true, get: function () { return model_probe_1.probeModelCatalog; } });
|
|
58
|
+
var ports_default_1 = require("./ports-default");
|
|
59
|
+
Object.defineProperty(exports, "CompositeEventSink", { enumerable: true, get: function () { return ports_default_1.CompositeEventSink; } });
|
|
60
|
+
Object.defineProperty(exports, "DirectoryWorkspaceProvider", { enumerable: true, get: function () { return ports_default_1.DirectoryWorkspaceProvider; } });
|
|
61
|
+
Object.defineProperty(exports, "NoArtifactInspector", { enumerable: true, get: function () { return ports_default_1.NoArtifactInspector; } });
|
|
62
|
+
Object.defineProperty(exports, "NoSecretProvider", { enumerable: true, get: function () { return ports_default_1.NoSecretProvider; } });
|
|
63
|
+
Object.defineProperty(exports, "NoopTelemetry", { enumerable: true, get: function () { return ports_default_1.NoopTelemetry; } });
|
|
64
|
+
Object.defineProperty(exports, "NullEventSink", { enumerable: true, get: function () { return ports_default_1.NullEventSink; } });
|
|
65
|
+
Object.defineProperty(exports, "ServerNotifier", { enumerable: true, get: function () { return ports_default_1.ServerNotifier; } });
|
|
66
|
+
Object.defineProperty(exports, "SessionTokenInjector", { enumerable: true, get: function () { return ports_default_1.SessionTokenInjector; } });
|
|
67
|
+
Object.defineProperty(exports, "StaticTaskMcpProvider", { enumerable: true, get: function () { return ports_default_1.StaticTaskMcpProvider; } });
|
|
68
|
+
var github_workspace_1 = require("./github-workspace");
|
|
69
|
+
Object.defineProperty(exports, "GitHubWorkspaceProvider", { enumerable: true, get: function () { return github_workspace_1.GitHubWorkspaceProvider; } });
|
|
70
|
+
/* ---- The runtime ---------------------------------------------------------- */
|
|
71
|
+
var mcp_auth_proxy_1 = require("./runtime/mcp-auth-proxy");
|
|
72
|
+
Object.defineProperty(exports, "McpAuthProxy", { enumerable: true, get: function () { return mcp_auth_proxy_1.McpAuthProxy; } });
|
|
73
|
+
var session_runner_1 = require("./runtime/session-runner");
|
|
74
|
+
Object.defineProperty(exports, "SessionRunner", { enumerable: true, get: function () { return session_runner_1.SessionRunner; } });
|
|
75
|
+
var projection_1 = require("./runtime/projection");
|
|
76
|
+
Object.defineProperty(exports, "projectParts", { enumerable: true, get: function () { return projection_1.projectParts; } });
|
|
77
|
+
Object.defineProperty(exports, "toIngestPart", { enumerable: true, get: function () { return projection_1.toIngestPart; } });
|
|
78
|
+
var child_stream_guards_1 = require("./runtime/child-stream-guards");
|
|
79
|
+
Object.defineProperty(exports, "guardChildStreams", { enumerable: true, get: function () { return child_stream_guards_1.guardChildStreams; } });
|
|
80
|
+
Object.defineProperty(exports, "guardStream", { enumerable: true, get: function () { return child_stream_guards_1.guardStream; } });
|
|
81
|
+
Object.defineProperty(exports, "isBenignStreamError", { enumerable: true, get: function () { return child_stream_guards_1.isBenignStreamError; } });
|
|
82
|
+
Object.defineProperty(exports, "writeToChildStdin", { enumerable: true, get: function () { return child_stream_guards_1.writeToChildStdin; } });
|
|
83
|
+
var sqlite_store_1 = require("./store/sqlite-store");
|
|
84
|
+
Object.defineProperty(exports, "MemoryTranscriptStore", { enumerable: true, get: function () { return sqlite_store_1.MemoryTranscriptStore; } });
|
|
85
|
+
Object.defineProperty(exports, "SqliteTranscriptStore", { enumerable: true, get: function () { return sqlite_store_1.SqliteTranscriptStore; } });
|
|
86
|
+
Object.defineProperty(exports, "openTranscriptStore", { enumerable: true, get: function () { return sqlite_store_1.openTranscriptStore; } });
|
|
87
|
+
/* ---- The backends --------------------------------------------------------- */
|
|
88
|
+
/**
|
|
89
|
+
* Extracted from 20x UNCHANGED — the whole `src/main/adapters/` directory had
|
|
90
|
+
* zero Electron imports, which is why the measurement put the extraction at two
|
|
91
|
+
* weeks rather than two months.
|
|
92
|
+
*/
|
|
93
|
+
__exportStar(require("./adapters"), exports);
|
|
94
|
+
/* ---- The wire contract ---------------------------------------------------- */
|
|
95
|
+
/**
|
|
96
|
+
* The control plane imports these too. The predicates are pure and SHARED on
|
|
97
|
+
* purpose, so the two sides cannot disagree about what "supported", "final" or
|
|
98
|
+
* "ephemeral" means. A rule written twice is a rule that drifts.
|
|
99
|
+
*/
|
|
100
|
+
var contract_1 = require("./contract");
|
|
101
|
+
Object.defineProperty(exports, "CONTRACT_ERROR_CODES", { enumerable: true, get: function () { return contract_1.CONTRACT_ERROR_CODES; } });
|
|
102
|
+
Object.defineProperty(exports, "DEFAULT_BASE_DELAY_MS", { enumerable: true, get: function () { return contract_1.DEFAULT_BASE_DELAY_MS; } });
|
|
103
|
+
Object.defineProperty(exports, "DEFAULT_HEARTBEAT_INTERVAL_MS", { enumerable: true, get: function () { return contract_1.DEFAULT_HEARTBEAT_INTERVAL_MS; } });
|
|
104
|
+
Object.defineProperty(exports, "DEFAULT_LEASE_TTL_MS", { enumerable: true, get: function () { return contract_1.DEFAULT_LEASE_TTL_MS; } });
|
|
105
|
+
Object.defineProperty(exports, "DEFAULT_LEASE_WAIT_SECONDS", { enumerable: true, get: function () { return contract_1.DEFAULT_LEASE_WAIT_SECONDS; } });
|
|
106
|
+
Object.defineProperty(exports, "DEFAULT_MAX_ATTEMPTS", { enumerable: true, get: function () { return contract_1.DEFAULT_MAX_ATTEMPTS; } });
|
|
107
|
+
Object.defineProperty(exports, "EPHEMERAL_PART_TYPES", { enumerable: true, get: function () { return contract_1.EPHEMERAL_PART_TYPES; } });
|
|
108
|
+
Object.defineProperty(exports, "FINAL_HTTP_STATUSES", { enumerable: true, get: function () { return contract_1.FINAL_HTTP_STATUSES; } });
|
|
109
|
+
Object.defineProperty(exports, "HARNESS_UPGRADE_EXIT_CODE", { enumerable: true, get: function () { return contract_1.HARNESS_UPGRADE_EXIT_CODE; } });
|
|
110
|
+
Object.defineProperty(exports, "MAX_INGEST_PARTS", { enumerable: true, get: function () { return contract_1.MAX_INGEST_PARTS; } });
|
|
111
|
+
Object.defineProperty(exports, "MAX_INGEST_PART_BYTES", { enumerable: true, get: function () { return contract_1.MAX_INGEST_PART_BYTES; } });
|
|
112
|
+
Object.defineProperty(exports, "MAX_LEASE_WAIT_SECONDS", { enumerable: true, get: function () { return contract_1.MAX_LEASE_WAIT_SECONDS; } });
|
|
113
|
+
Object.defineProperty(exports, "MIN_SUPPORTED_PROTOCOL_VERSION", { enumerable: true, get: function () { return contract_1.MIN_SUPPORTED_PROTOCOL_VERSION; } });
|
|
114
|
+
Object.defineProperty(exports, "MISSED_HEARTBEATS_BEFORE_OFFLINE", { enumerable: true, get: function () { return contract_1.MISSED_HEARTBEATS_BEFORE_OFFLINE; } });
|
|
115
|
+
Object.defineProperty(exports, "PERSISTED_PART_TYPES", { enumerable: true, get: function () { return contract_1.PERSISTED_PART_TYPES; } });
|
|
116
|
+
Object.defineProperty(exports, "PROTOCOL_VERSION", { enumerable: true, get: function () { return contract_1.PROTOCOL_VERSION; } });
|
|
117
|
+
Object.defineProperty(exports, "PROTOCOL_VERSION_HEADER", { enumerable: true, get: function () { return contract_1.PROTOCOL_VERSION_HEADER; } });
|
|
118
|
+
Object.defineProperty(exports, "ROUTES", { enumerable: true, get: function () { return contract_1.ROUTES; } });
|
|
119
|
+
Object.defineProperty(exports, "ingestPartByteLength", { enumerable: true, get: function () { return contract_1.ingestPartByteLength; } });
|
|
120
|
+
Object.defineProperty(exports, "isEphemeralPartType", { enumerable: true, get: function () { return contract_1.isEphemeralPartType; } });
|
|
121
|
+
Object.defineProperty(exports, "isFinalStatus", { enumerable: true, get: function () { return contract_1.isFinalStatus; } });
|
|
122
|
+
Object.defineProperty(exports, "isPersistablePart", { enumerable: true, get: function () { return contract_1.isPersistablePart; } });
|
|
123
|
+
Object.defineProperty(exports, "isProtocolSupported", { enumerable: true, get: function () { return contract_1.isProtocolSupported; } });
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE MODEL PROBE — how a runner reports the models its backends can drive.
|
|
3
|
+
*
|
|
4
|
+
* This is the harness half of the same feature 20x already has. 20x's
|
|
5
|
+
* `AgentForm` asks its AgentManager to `getProviders()` and flattens the
|
|
6
|
+
* answer; the runner carries the answer in `RunnerCapabilities` so the
|
|
7
|
+
* workflow-builder control plane can render exactly what THIS machine offers —
|
|
8
|
+
* and nothing else, because model source follows the harness, not a union
|
|
9
|
+
* across runners.
|
|
10
|
+
*
|
|
11
|
+
* THREE RULES, TAKEN FROM THE TASK THAT BUILT THE 20x SIDE:
|
|
12
|
+
*
|
|
13
|
+
* 1. `provider.models` MAY BE AN ARRAY OR AN OBJECT MAP. The object form is how
|
|
14
|
+
* custom providers (routerAI and similar) arrive, and `Array.isArray` alone
|
|
15
|
+
* silently yields nothing for them. Both branches are handled, using
|
|
16
|
+
* `Object.entries` exactly as 20x does so the map key serves as the model id
|
|
17
|
+
* when the value carries none.
|
|
18
|
+
*
|
|
19
|
+
* 2. Model ids are namespaced by provider (`${provider.id}/${model.id}`), the
|
|
20
|
+
* same prefix 20x applies. The gateway's own catalogue comes back with the
|
|
21
|
+
* `peakflo` prefix, which is how a runner's list and the control-plane
|
|
22
|
+
* gateway list agree on what "the same model" is.
|
|
23
|
+
*
|
|
24
|
+
* 3. DEGRADE HONESTLY, NEVER GUESS. A probe that fails (SDK absent, server
|
|
25
|
+
* unreachable, adapter without a provider listing) returns null, which the
|
|
26
|
+
* caller reports as "no models". The control plane and the UI already fall
|
|
27
|
+
* back to the gateway list and then to free text, so an empty report is safe
|
|
28
|
+
* and a wrong one is not.
|
|
29
|
+
*
|
|
30
|
+
* PROTECTING THE RUNNER FROM ITS OWN PROBE — the one that matters:
|
|
31
|
+
*
|
|
32
|
+
* Pi model discovery uses a short-lived no-session RPC process. This remains a
|
|
33
|
+
* START-UP/INTERVAL operation, never a per-heartbeat one: the harness caches
|
|
34
|
+
* the result and re-probes on a long interval or when Pi first appears.
|
|
35
|
+
*/
|
|
36
|
+
import type { CodingAgentAdapter } from './adapters/coding-agent-adapter';
|
|
37
|
+
import type { RunnerBackend, RunnerModel } from './types';
|
|
38
|
+
/**
|
|
39
|
+
* Build one adapter for probing. `buildAdapter` returns null when the host
|
|
40
|
+
* cannot wire this backend (unknown name, missing binary, an adapter that
|
|
41
|
+
* throws on the build) — a null is "no models", never an error.
|
|
42
|
+
*/
|
|
43
|
+
export type ModelProbeAdapterFactory = (backend: string) => CodingAgentAdapter | null;
|
|
44
|
+
export interface ProviderRecord {
|
|
45
|
+
id: string;
|
|
46
|
+
name?: string;
|
|
47
|
+
models?: unknown;
|
|
48
|
+
[key: string]: unknown;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* FLATTEN PROVIDERS TO A MODEL LIST, THE WAY 20x DOES.
|
|
52
|
+
*
|
|
53
|
+
* Handles BOTH shapes of `provider.models`:
|
|
54
|
+
* - an ARRAY of `{ id, name }` — the common case;
|
|
55
|
+
* - an OBJECT MAP of `modelId -> { name }` — how custom providers arrive
|
|
56
|
+
* (routerAI etc.), where the map's KEY is the authoritative id.
|
|
57
|
+
*
|
|
58
|
+
* Model ids are namespaced `provider.id/model[id]`; names read
|
|
59
|
+
* `provider.name - model.name|model id`.
|
|
60
|
+
*/
|
|
61
|
+
export declare function flattenProviderModels(providers: ProviderRecord[] | null | undefined): RunnerModel[];
|
|
62
|
+
/**
|
|
63
|
+
* What a probe learned about one runner, at one point in time.
|
|
64
|
+
*
|
|
65
|
+
* `byBackend` is the per-`backend` model list; `flat` is the deduplicated
|
|
66
|
+
* union the control-plane picker reads directly. `attempted` records every
|
|
67
|
+
* backend we TRIED to probe — used by the harness to know when a new backend
|
|
68
|
+
* (installed after start) needs a re-probe without re-probing ones that have
|
|
69
|
+
* nothing to give.
|
|
70
|
+
*/
|
|
71
|
+
export interface ModelCatalog {
|
|
72
|
+
byBackend: Record<string, RunnerModel[]>;
|
|
73
|
+
flat: RunnerModel[];
|
|
74
|
+
attempted: string[];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Probe ONE backend for the models it can drive. Returns [] (never null) so a
|
|
78
|
+
* failed probe is indistinguishable to the caller from "this backend lists
|
|
79
|
+
* nothing".
|
|
80
|
+
*
|
|
81
|
+
* The probe builds a FRESH adapter via the factory (never reuses a turn's),
|
|
82
|
+
* asks for its provider listing and flattens it. Pi's `getProviders` closes its
|
|
83
|
+
* own no-session discovery process before returning.
|
|
84
|
+
*/
|
|
85
|
+
export declare function probeBackendModels(backendName: string, buildAdapter: ModelProbeAdapterFactory): Promise<RunnerModel[]>;
|
|
86
|
+
/**
|
|
87
|
+
* Probe every installed backend and build the catalogue.
|
|
88
|
+
*
|
|
89
|
+
* NEVER REJECTS. Every individual backend failure degrades to an empty list
|
|
90
|
+
* for that backend; an entirely failing host yields a catalogue with no
|
|
91
|
+
* models, which is the honest answer.
|
|
92
|
+
*
|
|
93
|
+
* Sequential by construction. The published harness advertises only Pi, so
|
|
94
|
+
* the real bound is one 30-second probe and no shared-server race exists.
|
|
95
|
+
*/
|
|
96
|
+
export declare function probeModelCatalog(backends: RunnerBackend[], buildAdapter: ModelProbeAdapterFactory): Promise<ModelCatalog>;
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* THE MODEL PROBE — how a runner reports the models its backends can drive.
|
|
4
|
+
*
|
|
5
|
+
* This is the harness half of the same feature 20x already has. 20x's
|
|
6
|
+
* `AgentForm` asks its AgentManager to `getProviders()` and flattens the
|
|
7
|
+
* answer; the runner carries the answer in `RunnerCapabilities` so the
|
|
8
|
+
* workflow-builder control plane can render exactly what THIS machine offers —
|
|
9
|
+
* and nothing else, because model source follows the harness, not a union
|
|
10
|
+
* across runners.
|
|
11
|
+
*
|
|
12
|
+
* THREE RULES, TAKEN FROM THE TASK THAT BUILT THE 20x SIDE:
|
|
13
|
+
*
|
|
14
|
+
* 1. `provider.models` MAY BE AN ARRAY OR AN OBJECT MAP. The object form is how
|
|
15
|
+
* custom providers (routerAI and similar) arrive, and `Array.isArray` alone
|
|
16
|
+
* silently yields nothing for them. Both branches are handled, using
|
|
17
|
+
* `Object.entries` exactly as 20x does so the map key serves as the model id
|
|
18
|
+
* when the value carries none.
|
|
19
|
+
*
|
|
20
|
+
* 2. Model ids are namespaced by provider (`${provider.id}/${model.id}`), the
|
|
21
|
+
* same prefix 20x applies. The gateway's own catalogue comes back with the
|
|
22
|
+
* `peakflo` prefix, which is how a runner's list and the control-plane
|
|
23
|
+
* gateway list agree on what "the same model" is.
|
|
24
|
+
*
|
|
25
|
+
* 3. DEGRADE HONESTLY, NEVER GUESS. A probe that fails (SDK absent, server
|
|
26
|
+
* unreachable, adapter without a provider listing) returns null, which the
|
|
27
|
+
* caller reports as "no models". The control plane and the UI already fall
|
|
28
|
+
* back to the gateway list and then to free text, so an empty report is safe
|
|
29
|
+
* and a wrong one is not.
|
|
30
|
+
*
|
|
31
|
+
* PROTECTING THE RUNNER FROM ITS OWN PROBE — the one that matters:
|
|
32
|
+
*
|
|
33
|
+
* Pi model discovery uses a short-lived no-session RPC process. This remains a
|
|
34
|
+
* START-UP/INTERVAL operation, never a per-heartbeat one: the harness caches
|
|
35
|
+
* the result and re-probes on a long interval or when Pi first appears.
|
|
36
|
+
*/
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.flattenProviderModels = flattenProviderModels;
|
|
39
|
+
exports.probeBackendModels = probeBackendModels;
|
|
40
|
+
exports.probeModelCatalog = probeModelCatalog;
|
|
41
|
+
const model_gateway_1 = require("./adapters/model-gateway");
|
|
42
|
+
/**
|
|
43
|
+
* FLATTEN PROVIDERS TO A MODEL LIST, THE WAY 20x DOES.
|
|
44
|
+
*
|
|
45
|
+
* Handles BOTH shapes of `provider.models`:
|
|
46
|
+
* - an ARRAY of `{ id, name }` — the common case;
|
|
47
|
+
* - an OBJECT MAP of `modelId -> { name }` — how custom providers arrive
|
|
48
|
+
* (routerAI etc.), where the map's KEY is the authoritative id.
|
|
49
|
+
*
|
|
50
|
+
* Model ids are namespaced `provider.id/model[id]`; names read
|
|
51
|
+
* `provider.name - model.name|model id`.
|
|
52
|
+
*/
|
|
53
|
+
function flattenProviderModels(providers) {
|
|
54
|
+
if (!Array.isArray(providers) || providers.length === 0) {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
const out = [];
|
|
58
|
+
for (const provider of providers) {
|
|
59
|
+
if (!provider || typeof provider.id !== 'string')
|
|
60
|
+
continue;
|
|
61
|
+
/*
|
|
62
|
+
* NEVER REPORT THE GATEWAY PROVIDER ID AS THIS RUNNER'S OWN.
|
|
63
|
+
*
|
|
64
|
+
* That id belongs to the control plane. The harness REBUILDS it for every
|
|
65
|
+
* turn from the lease's gatewayUrl and per-turn key, so whatever a machine
|
|
66
|
+
* happens to have under the same name locally is overwritten before any
|
|
67
|
+
* model is called.
|
|
68
|
+
*
|
|
69
|
+
* Reporting it anyway offered models that could not run. This machine's
|
|
70
|
+
* local `peakflo` provider points at a different deployment and listed
|
|
71
|
+
* `vertex_ai/gemini-3-flash-preview`; the gateway the lease actually uses
|
|
72
|
+
* serves three models and not that one, so selecting it failed the turn
|
|
73
|
+
* with `Invalid model name passed in model=vertex_ai/gemini-3-flash-preview`.
|
|
74
|
+
* Two different providers were colliding under one prefix and the picker
|
|
75
|
+
* gave the user no way to tell them apart.
|
|
76
|
+
*
|
|
77
|
+
* The gateway's real catalogue reaches the UI on its own path, so dropping
|
|
78
|
+
* it here loses nothing and removes ids that cannot be called.
|
|
79
|
+
*/
|
|
80
|
+
if (provider.id === model_gateway_1.ENTERPRISE_AI_GATEWAY_PROVIDER_ID)
|
|
81
|
+
continue;
|
|
82
|
+
const raw = provider.models;
|
|
83
|
+
if (!raw)
|
|
84
|
+
continue;
|
|
85
|
+
if (Array.isArray(raw)) {
|
|
86
|
+
for (const m of raw) {
|
|
87
|
+
const entry = m;
|
|
88
|
+
const id = typeof entry?.id === 'string' && entry.id.length > 0
|
|
89
|
+
? entry.id
|
|
90
|
+
: null;
|
|
91
|
+
if (!id)
|
|
92
|
+
continue;
|
|
93
|
+
out.push({
|
|
94
|
+
id: `${provider.id}/${id}`,
|
|
95
|
+
name: `${provider.name ?? provider.id} - ${typeof entry?.name === 'string' ? entry.name : id}`
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else if (typeof raw === 'object') {
|
|
100
|
+
// Object form — model ids are the map KEYS; the value may carry a nicer
|
|
101
|
+
// name and (occasionally) its own id. 20x's Object.entries handling.
|
|
102
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
103
|
+
const entry = value;
|
|
104
|
+
const modelId = typeof entry?.id === 'string' && entry.id ? entry.id : key;
|
|
105
|
+
if (!modelId)
|
|
106
|
+
continue;
|
|
107
|
+
out.push({
|
|
108
|
+
id: `${provider.id}/${modelId}`,
|
|
109
|
+
name: `${provider.name ?? provider.id} - ${typeof entry?.name === 'string' ? entry.name : modelId}`
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
/** How long a single provider listing may take (includes a server cold start). */
|
|
117
|
+
const MODEL_PROBE_TIMEOUT_MS = 30_000;
|
|
118
|
+
async function withTimeout(promise, ms) {
|
|
119
|
+
let timer = null;
|
|
120
|
+
try {
|
|
121
|
+
return await Promise.race([
|
|
122
|
+
promise,
|
|
123
|
+
new Promise((_resolve, reject) => {
|
|
124
|
+
timer = setTimeout(() => reject(new Error('model probe timed out')), ms);
|
|
125
|
+
})
|
|
126
|
+
]);
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
if (timer)
|
|
130
|
+
clearTimeout(timer);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Probe ONE backend for the models it can drive. Returns [] (never null) so a
|
|
135
|
+
* failed probe is indistinguishable to the caller from "this backend lists
|
|
136
|
+
* nothing".
|
|
137
|
+
*
|
|
138
|
+
* The probe builds a FRESH adapter via the factory (never reuses a turn's),
|
|
139
|
+
* asks for its provider listing and flattens it. Pi's `getProviders` closes its
|
|
140
|
+
* own no-session discovery process before returning.
|
|
141
|
+
*/
|
|
142
|
+
async function probeBackendModels(backendName, buildAdapter) {
|
|
143
|
+
let adapter = null;
|
|
144
|
+
try {
|
|
145
|
+
adapter = buildAdapter(backendName);
|
|
146
|
+
if (!adapter || typeof adapter.getProviders !== 'function') {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
const result = await withTimeout(adapter.getProviders(), MODEL_PROBE_TIMEOUT_MS);
|
|
150
|
+
if (!result?.providers) {
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
return flattenProviderModels(result.providers);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// An adapter that throws is "this backend lists nothing here". NEVER a
|
|
157
|
+
// guess at what it might list, and never a reason to fail registration.
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Probe every installed backend and build the catalogue.
|
|
163
|
+
*
|
|
164
|
+
* NEVER REJECTS. Every individual backend failure degrades to an empty list
|
|
165
|
+
* for that backend; an entirely failing host yields a catalogue with no
|
|
166
|
+
* models, which is the honest answer.
|
|
167
|
+
*
|
|
168
|
+
* Sequential by construction. The published harness advertises only Pi, so
|
|
169
|
+
* the real bound is one 30-second probe and no shared-server race exists.
|
|
170
|
+
*/
|
|
171
|
+
async function probeModelCatalog(backends, buildAdapter) {
|
|
172
|
+
const byBackend = {};
|
|
173
|
+
const attempted = [];
|
|
174
|
+
const flatById = new Map();
|
|
175
|
+
for (const backend of backends) {
|
|
176
|
+
if (!backend?.name)
|
|
177
|
+
continue;
|
|
178
|
+
attempted.push(backend.name);
|
|
179
|
+
const models = await probeBackendModels(backend.name, buildAdapter);
|
|
180
|
+
if (models.length === 0) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
byBackend[backend.name] = models;
|
|
184
|
+
for (const model of models) {
|
|
185
|
+
if (!flatById.has(model.id)) {
|
|
186
|
+
flatById.set(model.id, model);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
byBackend,
|
|
192
|
+
flat: [...flatById.values()],
|
|
193
|
+
attempted
|
|
194
|
+
};
|
|
195
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { HarnessOptions } from './types';
|
|
2
|
+
export declare class OptionsError extends Error {
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Flags win over environment variables, so a local developer can override a
|
|
6
|
+
* value that pf-bootstrap injected.
|
|
7
|
+
*
|
|
8
|
+
* NOTE ON THE ENROLMENT TOKEN: it is accepted on the command line for local
|
|
9
|
+
* development and self-hosted installs, which is what the design calls for.
|
|
10
|
+
* In a managed sandbox pf-bootstrap passes it through PF_ENROLMENT_TOKEN
|
|
11
|
+
* instead, so it never appears in the process arguments — /proc/<pid>/cmdline
|
|
12
|
+
* is world-readable, while /proc/<pid>/environ is readable only by the
|
|
13
|
+
* process owner.
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseOptions(argv: string[], env: NodeJS.ProcessEnv): HarnessOptions;
|