@omercnet/paseo-omp 0.3.0 → 0.4.0-next.114.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/README.md +19 -5
- package/client/omp-config-surface.tsx +243 -24
- package/client/omp-config-views.ts +24 -0
- package/client/omp-model-picker-state.ts +145 -0
- package/client/omp-model-picker.tsx +282 -0
- package/client/omp-routing-editor.tsx +307 -0
- package/client/support-diagnostics-state.ts +45 -0
- package/index.server.ts +27 -2
- package/package.json +2 -8
- package/paseo-plugin.json +1 -1
- package/server/omp-models.ts +59 -0
- package/server/omp-settings.ts +30 -20
- package/server/operational-failure-diagnostics.ts +76 -0
- package/server/package-version.ts +2 -0
- package/server/protocol-violation-diagnostics.ts +169 -0
- package/server/provider/catalog.ts +39 -10
- package/server/provider/connection.ts +60 -8
- package/server/provider/host-tools.ts +284 -34
- package/server/provider/mcp-transport.ts +2 -1
- package/server/provider/omp-rpc.ts +1011 -107
- package/server/provider/profile-providers.ts +7 -2
- package/server/provider/registration.ts +12 -2
- package/server/provider/security.ts +8 -10
- package/server/provider/session-descriptors.ts +45 -11
- package/server/provider/session.ts +200 -58
- package/server/provider/subsessions.ts +311 -73
- package/server/provider/timeline-projector.ts +34 -11
- package/server/support-diagnostics.ts +284 -0
- package/shared/omp-models.ts +49 -0
- package/shared/omp-settings.ts +227 -3
- package/shared/support-diagnostics.ts +32 -0
- package/CHANGELOG.md +0 -113
- package/SUPPORT.md +0 -44
- package/TESTING.md +0 -150
- package/docs/alpha-release-checklist.md +0 -68
- package/docs/configuration.md +0 -126
- package/docs/core-provider-issue-audit.md +0 -109
- package/docs/images/mcp-authorization-compact.png +0 -0
- package/docs/images/mcp-controls-wide.png +0 -0
- package/docs/images/plugin-manager.png +0 -0
- package/docs/images/workspace-settings.png +0 -0
- package/docs/installation.md +0 -89
- package/tsconfig.json +0 -16
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { arch, platform } from "node:os";
|
|
2
|
+
import type { RpcInput } from "@getpaseo/plugin";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import type { OmpProviderHealth, OmpVersion } from "../shared/provider-diagnostics";
|
|
5
|
+
import {
|
|
6
|
+
type getOmpSupportReport,
|
|
7
|
+
OMP_SUPPORT_REPORT_MAX_BYTES,
|
|
8
|
+
OMP_SUPPORT_REPORT_SCHEMA_VERSION,
|
|
9
|
+
supportReportByteLength,
|
|
10
|
+
} from "../shared/support-diagnostics";
|
|
11
|
+
import type {
|
|
12
|
+
OmpOperationalFailureCollector,
|
|
13
|
+
OmpOperationalFailureSummary,
|
|
14
|
+
} from "./operational-failure-diagnostics";
|
|
15
|
+
import { PASEO_OMP_PACKAGE_VERSION } from "./package-version";
|
|
16
|
+
import type {
|
|
17
|
+
OmpProtocolViolationCollector,
|
|
18
|
+
OmpProtocolViolationSummary,
|
|
19
|
+
} from "./protocol-violation-diagnostics";
|
|
20
|
+
import { resolveGetOmpProviderHealth } from "./provider-diagnostics";
|
|
21
|
+
|
|
22
|
+
const PACKAGE_VERSION = z
|
|
23
|
+
.string()
|
|
24
|
+
.regex(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]{1,48})?$/u)
|
|
25
|
+
.max(64);
|
|
26
|
+
const NODE_VERSION = z
|
|
27
|
+
.string()
|
|
28
|
+
.regex(/^v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]{1,48})?$/u)
|
|
29
|
+
.max(64);
|
|
30
|
+
const KNOWN_PLATFORMS: Record<string, true> = {
|
|
31
|
+
aix: true,
|
|
32
|
+
android: true,
|
|
33
|
+
darwin: true,
|
|
34
|
+
freebsd: true,
|
|
35
|
+
linux: true,
|
|
36
|
+
openbsd: true,
|
|
37
|
+
sunos: true,
|
|
38
|
+
win32: true,
|
|
39
|
+
};
|
|
40
|
+
const KNOWN_ARCHITECTURES: Record<string, true> = {
|
|
41
|
+
arm: true,
|
|
42
|
+
arm64: true,
|
|
43
|
+
ia32: true,
|
|
44
|
+
loong64: true,
|
|
45
|
+
mips: true,
|
|
46
|
+
mipsel: true,
|
|
47
|
+
ppc: true,
|
|
48
|
+
ppc64: true,
|
|
49
|
+
riscv64: true,
|
|
50
|
+
s390: true,
|
|
51
|
+
s390x: true,
|
|
52
|
+
x64: true,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export interface OmpSupportReportData {
|
|
56
|
+
collectedAt: string;
|
|
57
|
+
pluginVersion: string;
|
|
58
|
+
platform: string;
|
|
59
|
+
architecture: string;
|
|
60
|
+
nodeVersion: string;
|
|
61
|
+
scope: "global" | "workspace";
|
|
62
|
+
store: "default" | "named-profile" | "custom-directory";
|
|
63
|
+
health: OmpProviderHealth | null;
|
|
64
|
+
violations: readonly OmpProtocolViolationSummary[];
|
|
65
|
+
operationalFailures: readonly OmpOperationalFailureSummary[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const EXPECTATION_LABELS: Record<
|
|
69
|
+
NonNullable<OmpProtocolViolationSummary["latestExpected"]>,
|
|
70
|
+
string
|
|
71
|
+
> = {
|
|
72
|
+
"complete-json-line": "newline-terminated JSON frame",
|
|
73
|
+
"within-byte-limit": "byte length within negotiated limit",
|
|
74
|
+
"valid-json": "valid UTF-8 JSON",
|
|
75
|
+
"object-envelope": "JSON object envelope",
|
|
76
|
+
"bounded-frame-type": "frame type string up to 64 bytes",
|
|
77
|
+
"valid-response-frame": "valid response envelope",
|
|
78
|
+
"valid-ready-frame": "valid ready handshake",
|
|
79
|
+
"single-ready-frame": "one ready handshake",
|
|
80
|
+
"valid-event-frame": "valid event payload",
|
|
81
|
+
"valid-event-state-transition": "event valid for current stream state",
|
|
82
|
+
"valid-message-event": "valid message event payload",
|
|
83
|
+
"valid-tool-event": "valid tool event payload",
|
|
84
|
+
"valid-lifecycle-event": "valid lifecycle event payload",
|
|
85
|
+
"valid-subagent-event": "valid subagent event payload",
|
|
86
|
+
"valid-configuration-event": "valid configuration event payload",
|
|
87
|
+
"valid-extension-ui-event": "valid extension UI event payload",
|
|
88
|
+
"known-event-type": "supported event type",
|
|
89
|
+
"notice-level-enum": "info, warning, or error",
|
|
90
|
+
"notice-message-string": "bounded string",
|
|
91
|
+
"valid-chunk-frame": "valid chunk metadata",
|
|
92
|
+
"valid-base64-chunk": "valid base64 chunk",
|
|
93
|
+
"first-chunk-index-zero": "first chunk index 0",
|
|
94
|
+
"contiguous-chunk-sequence": "contiguous chunk sequence",
|
|
95
|
+
"declared-chunk-byte-count": "decoded bytes equal declared byte length",
|
|
96
|
+
"chunk-before-deadline": "next chunk before timeout",
|
|
97
|
+
"no-interleaved-frame": "no interleaved frame during chunk assembly",
|
|
98
|
+
"no-remote-frame-error": "no remote frame error",
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
function finiteCount(value: number | null | undefined, maximum = Number.MAX_SAFE_INTEGER): string {
|
|
102
|
+
return value !== null && value !== undefined && Number.isSafeInteger(value) && value >= 0
|
|
103
|
+
? String(Math.min(value, maximum))
|
|
104
|
+
: "unknown";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function usefulReportLine(line: string): boolean {
|
|
108
|
+
return !line.endsWith(": unavailable") && !line.endsWith(": unknown") && !line.endsWith(": 0");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function formatVersion(version: OmpVersion | null): string {
|
|
112
|
+
if (!version) return "unavailable";
|
|
113
|
+
const core = `${version.major}.${version.minor}.${version.patch}`;
|
|
114
|
+
return version.prerelease ? `${core}-${version.prerelease}` : core;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function formatHealth(health: OmpProviderHealth | null): string[] {
|
|
118
|
+
if (!health) return ["collection.provider_health: failed"];
|
|
119
|
+
return [
|
|
120
|
+
"collection.provider_health: complete",
|
|
121
|
+
`omp.installed: ${health.binary.installed}`,
|
|
122
|
+
`omp.version: ${formatVersion(health.binary.version)}`,
|
|
123
|
+
`omp.version_probe: ${health.binary.versionStatus}`,
|
|
124
|
+
`omp.process_cleanup: ${health.binary.processCleanupFailed ? "failed" : "ok"}`,
|
|
125
|
+
`compatibility.rpc_ui: ${!health.rpcUi.checked || health.rpcUi.supported === null ? "unknown" : health.rpcUi.supported ? "supported" : "not-advertised"}`,
|
|
126
|
+
`compatibility.lsp: ${health.lsp.status}`,
|
|
127
|
+
`mcp.status: ${health.mcp.status}`,
|
|
128
|
+
`mcp.server_count: ${finiteCount(health.mcp.serverCount, 4_096)}`,
|
|
129
|
+
`mcp.reason: ${health.mcp.reason ?? "unknown"}`,
|
|
130
|
+
`storage.agent_root: ${health.roots.agentRootState}`,
|
|
131
|
+
`storage.config: ${health.roots.configState}`,
|
|
132
|
+
`storage.session_root: ${health.roots.sessionRootState}`,
|
|
133
|
+
`storage.agent_database: ${health.databases.agentDbState}`,
|
|
134
|
+
`storage.history_database: ${health.databases.historyDbState}`,
|
|
135
|
+
`storage.memory_backend: ${health.memoryBackend ?? "unknown"}`,
|
|
136
|
+
`hub.status: ${health.process.status}`,
|
|
137
|
+
`hub.tracked_count: ${finiteCount(health.process.trackedCount, 100_000)}`,
|
|
138
|
+
`hub.active_count: ${finiteCount(health.process.activeCount, 100_000)}`,
|
|
139
|
+
`hub.historical_count: ${finiteCount(health.process.historicalCount, 100_000)}`,
|
|
140
|
+
`hub.unknown_count: ${finiteCount(health.process.unknownCount, 100_000)}`,
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Stable line ordering is part of the pasted support-report contract. */
|
|
145
|
+
export function formatOmpSupportReport(data: OmpSupportReportData): string {
|
|
146
|
+
const lines = [
|
|
147
|
+
"OMP support diagnostics",
|
|
148
|
+
`schema_version: ${OMP_SUPPORT_REPORT_SCHEMA_VERSION}`,
|
|
149
|
+
`collected_at_utc: ${data.collectedAt}`,
|
|
150
|
+
`paseo_omp.version: ${data.pluginVersion}`,
|
|
151
|
+
`runtime.platform: ${data.platform}`,
|
|
152
|
+
`runtime.architecture: ${data.architecture}`,
|
|
153
|
+
`runtime.node: ${data.nodeVersion}`,
|
|
154
|
+
`selection.scope: ${data.scope}`,
|
|
155
|
+
`selection.store: ${data.store}`,
|
|
156
|
+
"selection.applies_to: provider-health",
|
|
157
|
+
"diagnostic_counters.scope: plugin-process-all-stores-workspaces",
|
|
158
|
+
"diagnostic_counters.lifetime: since-plugin-load",
|
|
159
|
+
...formatHealth(data.health),
|
|
160
|
+
];
|
|
161
|
+
for (const violation of data.violations) {
|
|
162
|
+
if (violation.occurrenceCount === 0) continue;
|
|
163
|
+
const prefix = `protocol.${violation.category}`;
|
|
164
|
+
lines.push(
|
|
165
|
+
`${prefix}.occurrence_count: ${finiteCount(violation.occurrenceCount)}`,
|
|
166
|
+
`${prefix}.batch_count: ${finiteCount(violation.batchCount)}`,
|
|
167
|
+
`${prefix}.max_batch_count: ${finiteCount(violation.maxOccurrenceCount)}`,
|
|
168
|
+
);
|
|
169
|
+
for (const [reason, count] of Object.entries(violation.reasonCounts)) {
|
|
170
|
+
if (count > 0) lines.push(`${prefix}.reason.${reason}.occurrence_count: ${count}`);
|
|
171
|
+
}
|
|
172
|
+
lines.push(
|
|
173
|
+
`${prefix}.latest_reason: ${violation.latestReason ?? "unknown"}`,
|
|
174
|
+
`${prefix}.latest_phase: ${violation.latestPhase ?? "unknown"}`,
|
|
175
|
+
`${prefix}.latest_event_type: ${violation.latestEventType ?? "unknown"}`,
|
|
176
|
+
`${prefix}.first_at_utc: ${violation.firstAt ?? "unavailable"}`,
|
|
177
|
+
`${prefix}.last_at_utc: ${violation.lastAt ?? "unavailable"}`,
|
|
178
|
+
`${prefix}.latest_frame_type: ${violation.latestFrameType ?? "unknown"}`,
|
|
179
|
+
`${prefix}.latest_field: ${violation.latestField ?? "unknown"}`,
|
|
180
|
+
`${prefix}.latest_expected: ${violation.latestExpected ? EXPECTATION_LABELS[violation.latestExpected] : "unknown"}`,
|
|
181
|
+
`${prefix}.latest_actual_type: ${violation.latestActualType ?? "unknown"}`,
|
|
182
|
+
`${prefix}.max_byte_size: ${finiteCount(violation.maxByteSize)}`,
|
|
183
|
+
`${prefix}.latest_limit_bytes: ${finiteCount(violation.latestLimitBytes)}`,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
for (const failure of data.operationalFailures) {
|
|
187
|
+
if (failure.occurrenceCount === 0) continue;
|
|
188
|
+
const prefix = `operational.${failure.category}.${failure.stage}`;
|
|
189
|
+
lines.push(
|
|
190
|
+
`${prefix}.occurrence_count: ${finiteCount(failure.occurrenceCount)}`,
|
|
191
|
+
`${prefix}.first_at_utc: ${failure.firstAt ?? "unavailable"}`,
|
|
192
|
+
`${prefix}.last_at_utc: ${failure.lastAt ?? "unavailable"}`,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
const report = `${lines.filter(usefulReportLine).join("\n")}\n`;
|
|
196
|
+
if (supportReportByteLength(report) > OMP_SUPPORT_REPORT_MAX_BYTES) {
|
|
197
|
+
return [
|
|
198
|
+
"OMP support diagnostics",
|
|
199
|
+
`schema_version: ${OMP_SUPPORT_REPORT_SCHEMA_VERSION}`,
|
|
200
|
+
`collected_at_utc: ${data.collectedAt}`,
|
|
201
|
+
"collection_status: failed",
|
|
202
|
+
"collection_error: report-size-limit",
|
|
203
|
+
"",
|
|
204
|
+
].join("\n");
|
|
205
|
+
}
|
|
206
|
+
return report;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function collectedAt(now: () => Date): string {
|
|
210
|
+
try {
|
|
211
|
+
const value = now();
|
|
212
|
+
return Number.isFinite(value.getTime()) ? value.toISOString() : "unavailable";
|
|
213
|
+
} catch {
|
|
214
|
+
return "unavailable";
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function resolveGetOmpSupportReport(
|
|
219
|
+
input: RpcInput<typeof getOmpSupportReport>,
|
|
220
|
+
violations: OmpProtocolViolationCollector,
|
|
221
|
+
operationalFailures: OmpOperationalFailureCollector,
|
|
222
|
+
dependencies: {
|
|
223
|
+
loadHealth?: typeof resolveGetOmpProviderHealth;
|
|
224
|
+
loadPluginVersion?: () => Promise<string>;
|
|
225
|
+
now?: () => Date;
|
|
226
|
+
platform?: () => string;
|
|
227
|
+
architecture?: () => string;
|
|
228
|
+
nodeVersion?: string;
|
|
229
|
+
} = {},
|
|
230
|
+
): Promise<{ report: string }> {
|
|
231
|
+
const timestamp = collectedAt(dependencies.now ?? (() => new Date()));
|
|
232
|
+
try {
|
|
233
|
+
const [healthResult, pluginVersionResult] = await Promise.allSettled([
|
|
234
|
+
(dependencies.loadHealth ?? resolveGetOmpProviderHealth)(input),
|
|
235
|
+
(dependencies.loadPluginVersion ?? (() => Promise.resolve(PASEO_OMP_PACKAGE_VERSION)))(),
|
|
236
|
+
]);
|
|
237
|
+
const platformValue = (dependencies.platform ?? platform)();
|
|
238
|
+
const architectureValue = (dependencies.architecture ?? arch)();
|
|
239
|
+
const nodeVersionValue = dependencies.nodeVersion ?? process.version;
|
|
240
|
+
return {
|
|
241
|
+
report: formatOmpSupportReport({
|
|
242
|
+
collectedAt: timestamp,
|
|
243
|
+
pluginVersion:
|
|
244
|
+
pluginVersionResult.status === "fulfilled" &&
|
|
245
|
+
PACKAGE_VERSION.safeParse(pluginVersionResult.value).success
|
|
246
|
+
? pluginVersionResult.value
|
|
247
|
+
: "unavailable",
|
|
248
|
+
platform: KNOWN_PLATFORMS[platformValue] ? platformValue : "unknown",
|
|
249
|
+
architecture: KNOWN_ARCHITECTURES[architectureValue] ? architectureValue : "unknown",
|
|
250
|
+
nodeVersion: NODE_VERSION.safeParse(nodeVersionValue).success
|
|
251
|
+
? nodeVersionValue
|
|
252
|
+
: "unavailable",
|
|
253
|
+
scope: input.cwd ? "workspace" : "global",
|
|
254
|
+
store: input.store?.profile
|
|
255
|
+
? "named-profile"
|
|
256
|
+
: input.store?.agentDir
|
|
257
|
+
? "custom-directory"
|
|
258
|
+
: "default",
|
|
259
|
+
health: healthResult.status === "fulfilled" ? healthResult.value : null,
|
|
260
|
+
violations: violations.snapshot(),
|
|
261
|
+
operationalFailures: operationalFailures.snapshot(),
|
|
262
|
+
}),
|
|
263
|
+
};
|
|
264
|
+
} catch {
|
|
265
|
+
return {
|
|
266
|
+
report: formatOmpSupportReport({
|
|
267
|
+
collectedAt: timestamp,
|
|
268
|
+
pluginVersion: "unavailable",
|
|
269
|
+
platform: "unknown",
|
|
270
|
+
architecture: "unknown",
|
|
271
|
+
nodeVersion: "unavailable",
|
|
272
|
+
scope: input.cwd ? "workspace" : "global",
|
|
273
|
+
store: input.store?.profile
|
|
274
|
+
? "named-profile"
|
|
275
|
+
: input.store?.agentDir
|
|
276
|
+
? "custom-directory"
|
|
277
|
+
: "default",
|
|
278
|
+
health: null,
|
|
279
|
+
violations: violations.snapshot(),
|
|
280
|
+
operationalFailures: operationalFailures.snapshot(),
|
|
281
|
+
}),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { defineRpc } from "@getpaseo/plugin";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { OmpWorkspaceCwdSchema } from "./hub";
|
|
4
|
+
import { OmpStoreSchema } from "./omp-store";
|
|
5
|
+
|
|
6
|
+
const OMP_MODEL_TEXT_LIMIT = 256;
|
|
7
|
+
const OMP_MODEL_SELECTOR_LIMIT = OMP_MODEL_TEXT_LIMIT * 2 + 1;
|
|
8
|
+
const OMP_MODEL_LIST_LIMIT = 256;
|
|
9
|
+
const OMP_MODEL_INPUT_LIMIT = 16;
|
|
10
|
+
const OMP_THINKING_LEVEL_LIMIT = 16;
|
|
11
|
+
const OMP_THINKING_LEVEL_TEXT_LIMIT = 32;
|
|
12
|
+
const OMP_CONTEXT_WINDOW_LIMIT = 100_000_000;
|
|
13
|
+
|
|
14
|
+
const OmpModelTextSchema = z.string().min(1).max(OMP_MODEL_TEXT_LIMIT);
|
|
15
|
+
|
|
16
|
+
export const OmpModelCandidateSchema = z
|
|
17
|
+
.object({
|
|
18
|
+
selector: z.string().min(3).max(OMP_MODEL_SELECTOR_LIMIT),
|
|
19
|
+
provider: OmpModelTextSchema,
|
|
20
|
+
id: OmpModelTextSchema,
|
|
21
|
+
name: z.string().max(OMP_MODEL_TEXT_LIMIT).optional(),
|
|
22
|
+
reasoning: z.boolean(),
|
|
23
|
+
input: z.array(OmpModelTextSchema).max(OMP_MODEL_INPUT_LIMIT),
|
|
24
|
+
contextWindow: z
|
|
25
|
+
.number()
|
|
26
|
+
.int()
|
|
27
|
+
.nonnegative()
|
|
28
|
+
.max(OMP_CONTEXT_WINDOW_LIMIT)
|
|
29
|
+
.nullable()
|
|
30
|
+
.optional(),
|
|
31
|
+
thinkingLevels: z
|
|
32
|
+
.array(z.string().max(OMP_THINKING_LEVEL_TEXT_LIMIT))
|
|
33
|
+
.max(OMP_THINKING_LEVEL_LIMIT),
|
|
34
|
+
})
|
|
35
|
+
.strict();
|
|
36
|
+
export type OmpModelCandidate = z.infer<typeof OmpModelCandidateSchema>;
|
|
37
|
+
|
|
38
|
+
export const OmpModelListResultSchema = z
|
|
39
|
+
.object({ models: z.array(OmpModelCandidateSchema).max(OMP_MODEL_LIST_LIMIT) })
|
|
40
|
+
.strict();
|
|
41
|
+
export type OmpModelListResult = z.infer<typeof OmpModelListResultSchema>;
|
|
42
|
+
|
|
43
|
+
export const listOmpModels = defineRpc({
|
|
44
|
+
name: "paseo-omp.list-models",
|
|
45
|
+
input: z
|
|
46
|
+
.object({ store: OmpStoreSchema.optional(), cwd: OmpWorkspaceCwdSchema.optional() })
|
|
47
|
+
.strict(),
|
|
48
|
+
output: OmpModelListResultSchema,
|
|
49
|
+
});
|
package/shared/omp-settings.ts
CHANGED
|
@@ -17,6 +17,163 @@ export type OmpSettingType = z.infer<typeof OmpSettingTypeSchema>;
|
|
|
17
17
|
export const OmpScalarValueSchema = z.union([z.boolean(), z.number(), z.string()]);
|
|
18
18
|
export type OmpScalarValue = z.infer<typeof OmpScalarValueSchema>;
|
|
19
19
|
|
|
20
|
+
const MAX_ROUTING_ENTRIES = 64;
|
|
21
|
+
const MAX_FALLBACK_CHAIN_LENGTH = 16;
|
|
22
|
+
const MAX_AGENT_MODEL_CHOICES = 8;
|
|
23
|
+
const MAX_MODEL_SELECTOR_LENGTH = 513;
|
|
24
|
+
|
|
25
|
+
function containsControlCharacter(value: string): boolean {
|
|
26
|
+
for (const character of value) {
|
|
27
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
28
|
+
if (codePoint < 32 || codePoint === 127) return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function utf8ByteLength(value: string): number {
|
|
34
|
+
let bytes = 0;
|
|
35
|
+
for (const character of value) {
|
|
36
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
37
|
+
bytes += codePoint <= 0x7f ? 1 : codePoint <= 0x7ff ? 2 : codePoint <= 0xffff ? 3 : 4;
|
|
38
|
+
}
|
|
39
|
+
return bytes;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const OmpRoleNameSchema = z
|
|
43
|
+
.string()
|
|
44
|
+
.min(1)
|
|
45
|
+
.max(64)
|
|
46
|
+
.regex(
|
|
47
|
+
/^[A-Za-z][A-Za-z0-9_-]*$/,
|
|
48
|
+
"Role names must start with a letter and use only letters, digits, _ or -.",
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
export const OmpModelSelectorSchema = z
|
|
52
|
+
.string()
|
|
53
|
+
.min(1)
|
|
54
|
+
.max(MAX_MODEL_SELECTOR_LENGTH)
|
|
55
|
+
.refine(
|
|
56
|
+
(value) => utf8ByteLength(value) <= MAX_MODEL_SELECTOR_LENGTH,
|
|
57
|
+
`Model selectors must not exceed ${MAX_MODEL_SELECTOR_LENGTH} UTF-8 bytes.`,
|
|
58
|
+
)
|
|
59
|
+
.refine(
|
|
60
|
+
(value) => value === value.trim() && !/\s/u.test(value) && !containsControlCharacter(value),
|
|
61
|
+
{
|
|
62
|
+
message: "Model selectors must be a single printable token.",
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
export function isValidOmpModelSelector(value: string): boolean {
|
|
67
|
+
return OmpModelSelectorSchema.safeParse(value).success;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const OmpAgentNameSchema = z
|
|
71
|
+
.string()
|
|
72
|
+
.min(1)
|
|
73
|
+
.max(128)
|
|
74
|
+
.refine((value) => value === value.trim() && !containsControlCharacter(value), {
|
|
75
|
+
message: "Agent names cannot have surrounding whitespace or control characters.",
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
export const OmpModelRolesSchema = z
|
|
79
|
+
.record(OmpRoleNameSchema, OmpModelSelectorSchema)
|
|
80
|
+
.refine((value) => Object.keys(value).length <= MAX_ROUTING_ENTRIES, "Too many model roles.");
|
|
81
|
+
|
|
82
|
+
const OmpFallbackKeySchema = z.string().superRefine((value, context) => {
|
|
83
|
+
const schema = value.includes("/") ? OmpModelSelectorSchema : OmpRoleNameSchema;
|
|
84
|
+
const result = schema.safeParse(value);
|
|
85
|
+
if (!result.success) {
|
|
86
|
+
context.addIssue({
|
|
87
|
+
code: "custom",
|
|
88
|
+
message: result.error.issues[0]?.message ?? "Invalid fallback chain key.",
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
export const OmpFallbackChainsSchema = z
|
|
94
|
+
.record(OmpFallbackKeySchema, z.array(OmpModelSelectorSchema).max(MAX_FALLBACK_CHAIN_LENGTH))
|
|
95
|
+
.refine((value) => Object.keys(value).length <= MAX_ROUTING_ENTRIES, "Too many fallback chains.");
|
|
96
|
+
|
|
97
|
+
export const OmpCycleOrderSchema = z
|
|
98
|
+
.array(OmpRoleNameSchema)
|
|
99
|
+
.max(MAX_ROUTING_ENTRIES)
|
|
100
|
+
.refine((value) => new Set(value).size === value.length, "Cycle roles must be unique.");
|
|
101
|
+
|
|
102
|
+
export const OmpAgentModelOverridesSchema = z
|
|
103
|
+
.record(
|
|
104
|
+
OmpAgentNameSchema,
|
|
105
|
+
z.union([
|
|
106
|
+
OmpModelSelectorSchema,
|
|
107
|
+
z.array(OmpModelSelectorSchema).min(1).max(MAX_AGENT_MODEL_CHOICES),
|
|
108
|
+
]),
|
|
109
|
+
)
|
|
110
|
+
.refine(
|
|
111
|
+
(value) => Object.keys(value).length <= MAX_ROUTING_ENTRIES,
|
|
112
|
+
"Too many agent model overrides.",
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
export const OmpAgentServiceTierOverridesSchema = z
|
|
116
|
+
.record(
|
|
117
|
+
OmpAgentNameSchema,
|
|
118
|
+
z.enum(["inherit", "none", "auto", "default", "flex", "scale", "priority"]),
|
|
119
|
+
)
|
|
120
|
+
.refine(
|
|
121
|
+
(value) => Object.keys(value).length <= MAX_ROUTING_ENTRIES,
|
|
122
|
+
"Too many agent service tier overrides.",
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const OmpAgentToggleOrSelectorSchema = z.union([z.enum(["on", "off"]), OmpModelSelectorSchema]);
|
|
126
|
+
|
|
127
|
+
export const OmpAgentPrewalkSchema = z
|
|
128
|
+
.record(OmpAgentNameSchema, OmpAgentToggleOrSelectorSchema)
|
|
129
|
+
.refine(
|
|
130
|
+
(value) => Object.keys(value).length <= MAX_ROUTING_ENTRIES,
|
|
131
|
+
"Too many agent prewalk overrides.",
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
export const OmpAgentAdvisorSchema = z
|
|
135
|
+
.record(OmpAgentNameSchema, OmpAgentToggleOrSelectorSchema)
|
|
136
|
+
.refine(
|
|
137
|
+
(value) => Object.keys(value).length <= MAX_ROUTING_ENTRIES,
|
|
138
|
+
"Too many agent advisor overrides.",
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
export const OMP_STRUCTURED_SETTING_PATHS = [
|
|
142
|
+
"modelRoles",
|
|
143
|
+
"retry.fallbackChains",
|
|
144
|
+
"cycleOrder",
|
|
145
|
+
"task.agentModelOverrides",
|
|
146
|
+
"task.agentServiceTierOverrides",
|
|
147
|
+
"task.agentPrewalk",
|
|
148
|
+
"task.agentAdvisor",
|
|
149
|
+
] as const;
|
|
150
|
+
export type OmpStructuredSettingPath = (typeof OMP_STRUCTURED_SETTING_PATHS)[number];
|
|
151
|
+
|
|
152
|
+
export function parseOmpStructuredSettingValue(path: string, value: unknown): unknown | undefined {
|
|
153
|
+
switch (path) {
|
|
154
|
+
case "modelRoles":
|
|
155
|
+
return OmpModelRolesSchema.safeParse(value).data;
|
|
156
|
+
case "retry.fallbackChains":
|
|
157
|
+
return OmpFallbackChainsSchema.safeParse(value).data;
|
|
158
|
+
case "cycleOrder":
|
|
159
|
+
return OmpCycleOrderSchema.safeParse(value).data;
|
|
160
|
+
case "task.agentModelOverrides":
|
|
161
|
+
return OmpAgentModelOverridesSchema.safeParse(value).data;
|
|
162
|
+
case "task.agentServiceTierOverrides":
|
|
163
|
+
return OmpAgentServiceTierOverridesSchema.safeParse(value).data;
|
|
164
|
+
case "task.agentPrewalk":
|
|
165
|
+
return OmpAgentPrewalkSchema.safeParse(value).data;
|
|
166
|
+
case "task.agentAdvisor":
|
|
167
|
+
return OmpAgentAdvisorSchema.safeParse(value).data;
|
|
168
|
+
default:
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function isOmpStructuredSettingPath(path: string): path is OmpStructuredSettingPath {
|
|
174
|
+
return (OMP_STRUCTURED_SETTING_PATHS as readonly string[]).includes(path);
|
|
175
|
+
}
|
|
176
|
+
|
|
20
177
|
export const OmpSettingSchema = z
|
|
21
178
|
.object({
|
|
22
179
|
path: z.string(),
|
|
@@ -184,9 +341,76 @@ export const listOmpSettings = defineRpc({
|
|
|
184
341
|
}),
|
|
185
342
|
});
|
|
186
343
|
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
344
|
+
const OmpScalarSettingPathSchema = z
|
|
345
|
+
.string()
|
|
346
|
+
.min(1)
|
|
347
|
+
.refine(
|
|
348
|
+
(path) => !isOmpStructuredSettingPath(path),
|
|
349
|
+
"Structured routing settings require typed values.",
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
const OmpStructuredSettingChangeSchema = z.discriminatedUnion("path", [
|
|
353
|
+
z
|
|
354
|
+
.object({
|
|
355
|
+
operation: z.literal("set"),
|
|
356
|
+
path: z.literal("modelRoles"),
|
|
357
|
+
value: OmpModelRolesSchema,
|
|
358
|
+
})
|
|
359
|
+
.strict(),
|
|
360
|
+
z
|
|
361
|
+
.object({
|
|
362
|
+
operation: z.literal("set"),
|
|
363
|
+
path: z.literal("retry.fallbackChains"),
|
|
364
|
+
value: OmpFallbackChainsSchema,
|
|
365
|
+
})
|
|
366
|
+
.strict(),
|
|
367
|
+
z
|
|
368
|
+
.object({
|
|
369
|
+
operation: z.literal("set"),
|
|
370
|
+
path: z.literal("cycleOrder"),
|
|
371
|
+
value: OmpCycleOrderSchema,
|
|
372
|
+
})
|
|
373
|
+
.strict(),
|
|
374
|
+
z
|
|
375
|
+
.object({
|
|
376
|
+
operation: z.literal("set"),
|
|
377
|
+
path: z.literal("task.agentModelOverrides"),
|
|
378
|
+
value: OmpAgentModelOverridesSchema,
|
|
379
|
+
})
|
|
380
|
+
.strict(),
|
|
381
|
+
z
|
|
382
|
+
.object({
|
|
383
|
+
operation: z.literal("set"),
|
|
384
|
+
path: z.literal("task.agentServiceTierOverrides"),
|
|
385
|
+
value: OmpAgentServiceTierOverridesSchema,
|
|
386
|
+
})
|
|
387
|
+
.strict(),
|
|
388
|
+
z
|
|
389
|
+
.object({
|
|
390
|
+
operation: z.literal("set"),
|
|
391
|
+
path: z.literal("task.agentPrewalk"),
|
|
392
|
+
value: OmpAgentPrewalkSchema,
|
|
393
|
+
})
|
|
394
|
+
.strict(),
|
|
395
|
+
z
|
|
396
|
+
.object({
|
|
397
|
+
operation: z.literal("set"),
|
|
398
|
+
path: z.literal("task.agentAdvisor"),
|
|
399
|
+
value: OmpAgentAdvisorSchema,
|
|
400
|
+
})
|
|
401
|
+
.strict(),
|
|
402
|
+
]);
|
|
403
|
+
|
|
404
|
+
const OmpSettingChangeSchema = z.union([
|
|
405
|
+
z
|
|
406
|
+
.object({
|
|
407
|
+
operation: z.literal("set"),
|
|
408
|
+
path: OmpScalarSettingPathSchema,
|
|
409
|
+
value: OmpScalarValueSchema,
|
|
410
|
+
})
|
|
411
|
+
.strict(),
|
|
412
|
+
OmpStructuredSettingChangeSchema,
|
|
413
|
+
z.object({ operation: z.literal("reset"), path: z.string().min(1) }).strict(),
|
|
190
414
|
]);
|
|
191
415
|
|
|
192
416
|
export const updateOmpSettings = defineRpc({
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { defineRpc } from "@getpaseo/plugin";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { OmpWorkspaceCwdSchema } from "./hub";
|
|
4
|
+
import { OmpStoreSchema } from "./omp-store";
|
|
5
|
+
|
|
6
|
+
export const OMP_SUPPORT_REPORT_SCHEMA_VERSION = 1 as const;
|
|
7
|
+
export const OMP_SUPPORT_REPORT_MAX_BYTES = 64 * 1024;
|
|
8
|
+
export const OMP_SUPPORT_ISSUE_URL =
|
|
9
|
+
"https://github.com/omercnet/paseo-plugins/issues/new?template=omp-plugin.yml";
|
|
10
|
+
|
|
11
|
+
export function supportReportByteLength(value: string): number {
|
|
12
|
+
return new TextEncoder().encode(value).byteLength;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const OmpSupportReportTextSchema = z
|
|
16
|
+
.string()
|
|
17
|
+
.min(1)
|
|
18
|
+
.refine((value) => supportReportByteLength(value) <= OMP_SUPPORT_REPORT_MAX_BYTES, {
|
|
19
|
+
message: "OMP support report exceeds 64 KiB",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export const getOmpSupportReport = defineRpc({
|
|
23
|
+
name: "paseo-omp.get-support-report",
|
|
24
|
+
input: z
|
|
25
|
+
.object({
|
|
26
|
+
store: OmpStoreSchema.optional(),
|
|
27
|
+
force: z.boolean().optional(),
|
|
28
|
+
cwd: OmpWorkspaceCwdSchema.optional(),
|
|
29
|
+
})
|
|
30
|
+
.strict(),
|
|
31
|
+
output: z.object({ report: OmpSupportReportTextSchema }).strict(),
|
|
32
|
+
});
|