@the-open-engine/zeroshot 6.22.0 → 6.23.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/lib/agent-cli-provider/adapters/omp.d.ts +3 -1
- package/lib/agent-cli-provider/adapters/omp.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/omp.js +252 -269
- package/lib/agent-cli-provider/adapters/omp.js.map +1 -1
- package/lib/agent-cli-provider/contract-invoke.d.ts.map +1 -1
- package/lib/agent-cli-provider/contract-invoke.js +68 -14
- package/lib/agent-cli-provider/contract-invoke.js.map +1 -1
- package/lib/agent-cli-provider/contract-options.d.ts.map +1 -1
- package/lib/agent-cli-provider/contract-options.js +5 -3
- package/lib/agent-cli-provider/contract-options.js.map +1 -1
- package/lib/agent-cli-provider/index.d.ts +6 -0
- package/lib/agent-cli-provider/index.d.ts.map +1 -1
- package/lib/agent-cli-provider/index.js +12 -1
- package/lib/agent-cli-provider/index.js.map +1 -1
- package/lib/agent-cli-provider/omp-release.d.ts +3 -1
- package/lib/agent-cli-provider/omp-release.d.ts.map +1 -1
- package/lib/agent-cli-provider/omp-release.js +22 -2
- package/lib/agent-cli-provider/omp-release.js.map +1 -1
- package/lib/agent-cli-provider/omp-rpc-bounds.d.ts +7 -0
- package/lib/agent-cli-provider/omp-rpc-bounds.d.ts.map +1 -0
- package/lib/agent-cli-provider/omp-rpc-bounds.js +27 -0
- package/lib/agent-cli-provider/omp-rpc-bounds.js.map +1 -0
- package/lib/agent-cli-provider/omp-rpc-driver.d.ts +40 -0
- package/lib/agent-cli-provider/omp-rpc-driver.d.ts.map +1 -0
- package/lib/agent-cli-provider/omp-rpc-driver.js +494 -0
- package/lib/agent-cli-provider/omp-rpc-driver.js.map +1 -0
- package/lib/agent-cli-provider/omp-rpc-events.d.ts +28 -0
- package/lib/agent-cli-provider/omp-rpc-events.d.ts.map +1 -0
- package/lib/agent-cli-provider/omp-rpc-events.js +264 -0
- package/lib/agent-cli-provider/omp-rpc-events.js.map +1 -0
- package/lib/agent-cli-provider/omp-rpc-protocol.d.ts +1 -1
- package/lib/agent-cli-provider/omp-rpc-protocol.d.ts.map +1 -1
- package/lib/agent-cli-provider/omp-rpc-protocol.js +8 -3
- package/lib/agent-cli-provider/omp-rpc-protocol.js.map +1 -1
- package/lib/agent-cli-provider/omp-rpc-session.d.ts +17 -0
- package/lib/agent-cli-provider/omp-rpc-session.d.ts.map +1 -0
- package/lib/agent-cli-provider/omp-rpc-session.js +6 -0
- package/lib/agent-cli-provider/omp-rpc-session.js.map +1 -0
- package/lib/agent-cli-provider/provider-registry.d.ts +15 -11
- package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
- package/lib/agent-cli-provider/provider-registry.js +16 -5
- package/lib/agent-cli-provider/provider-registry.js.map +1 -1
- package/lib/agent-cli-provider/types.d.ts +13 -11
- package/lib/agent-cli-provider/types.d.ts.map +1 -1
- package/lib/agent-cli-provider/types.js.map +1 -1
- package/package.json +2 -1
- package/src/agent-cli-provider/adapters/omp.ts +276 -329
- package/src/agent-cli-provider/contract-invoke.ts +86 -15
- package/src/agent-cli-provider/contract-options.ts +5 -3
- package/src/agent-cli-provider/index.ts +13 -0
- package/src/agent-cli-provider/omp-release.ts +24 -1
- package/src/agent-cli-provider/omp-rpc-bounds.ts +28 -0
- package/src/agent-cli-provider/omp-rpc-driver.ts +611 -0
- package/src/agent-cli-provider/omp-rpc-events.ts +318 -0
- package/src/agent-cli-provider/omp-rpc-protocol.ts +8 -3
- package/src/agent-cli-provider/omp-rpc-session.ts +20 -0
- package/src/agent-cli-provider/provider-registry.ts +26 -7
- package/src/agent-cli-provider/types.ts +14 -11
- package/src/command-cleanup-ownership.js +307 -0
- package/src/omp-config-overlay.js +96 -0
- package/src/orchestrator.js +20 -5
- package/src/preflight.js +35 -3
- package/src/watcher-prompt-channel.js +209 -0
- package/task-lib/command-spec-cleanup.js +1 -262
- package/task-lib/provider-helper-runtime.js +6 -0
- package/task-lib/rpc-watcher.js +186 -0
- package/task-lib/runner.js +40 -6
- package/task-lib/watcher-output-runtime.js +32 -0
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
import { appendJsonSchemaPrompt } from '../schema';
|
|
2
1
|
import { contractError } from '../contract-errors';
|
|
2
|
+
import { UnsupportedProviderCapabilityError } from '../errors';
|
|
3
|
+
import { appendJsonSchemaPrompt } from '../schema';
|
|
4
|
+
import { isRecord, unknownToMessage } from '../json';
|
|
5
|
+
import { OMP_REMEDIATION, OMP_SUPPORTED_VERSION } from '../omp-release';
|
|
6
|
+
import { parseNormalizedOmpRpcEventLine } from '../omp-rpc-events';
|
|
3
7
|
import {
|
|
4
|
-
|
|
5
|
-
getBoolean,
|
|
6
|
-
getNumber,
|
|
7
|
-
getOptionalString,
|
|
8
|
-
getRecord,
|
|
9
|
-
getString,
|
|
10
|
-
isRecord,
|
|
11
|
-
tryParseJson,
|
|
12
|
-
unknownToMessage,
|
|
13
|
-
} from '../json';
|
|
14
|
-
import {
|
|
8
|
+
InvalidProviderModelError,
|
|
15
9
|
type BuildProviderCommandOptions,
|
|
16
10
|
type CommandSpec,
|
|
17
11
|
type ErrorClassification,
|
|
@@ -20,8 +14,8 @@ import {
|
|
|
20
14
|
type ModelCatalogEntry,
|
|
21
15
|
type ModelLevel,
|
|
22
16
|
type OmpCliFeatures,
|
|
23
|
-
type OutputEvent,
|
|
24
17
|
type ProviderAdapter,
|
|
18
|
+
type ProviderParseResult,
|
|
25
19
|
type ProviderParserState,
|
|
26
20
|
type ResolvedModelSpec,
|
|
27
21
|
type WarningMetadata,
|
|
@@ -35,62 +29,176 @@ import {
|
|
|
35
29
|
warning,
|
|
36
30
|
} from './common';
|
|
37
31
|
|
|
32
|
+
interface OmpConfigOverlay {
|
|
33
|
+
readonly dir: string;
|
|
34
|
+
readonly file: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type UnknownFunction = (...args: unknown[]) => unknown;
|
|
38
|
+
|
|
39
|
+
function isUnknownFunction(value: unknown): value is UnknownFunction {
|
|
40
|
+
return typeof value === 'function';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createOmpConfigOverlay(): OmpConfigOverlay {
|
|
44
|
+
const overlayModule: unknown = require('../../../src/omp-config-overlay');
|
|
45
|
+
if (!isRecord(overlayModule) || !isUnknownFunction(overlayModule.createOmpConfigOverlay)) {
|
|
46
|
+
throw new Error('src/omp-config-overlay must export createOmpConfigOverlay.');
|
|
47
|
+
}
|
|
48
|
+
const overlay = overlayModule.createOmpConfigOverlay();
|
|
49
|
+
if (
|
|
50
|
+
!isRecord(overlay) ||
|
|
51
|
+
typeof overlay.dir !== 'string' ||
|
|
52
|
+
typeof overlay.file !== 'string'
|
|
53
|
+
) {
|
|
54
|
+
throw new Error('createOmpConfigOverlay() must return { dir, file } strings.');
|
|
55
|
+
}
|
|
56
|
+
return { dir: overlay.dir, file: overlay.file };
|
|
57
|
+
}
|
|
58
|
+
|
|
38
59
|
const MODEL_CATALOG: Readonly<Record<string, ModelCatalogEntry>> = {};
|
|
39
60
|
|
|
40
61
|
const LEVEL_MAPPING: Readonly<Record<ModelLevel, LevelModelSpec>> = {
|
|
41
|
-
level1: { rank: 1, model:
|
|
42
|
-
level2: { rank: 2, model:
|
|
43
|
-
level3: { rank: 3, model:
|
|
62
|
+
level1: { rank: 1, model: '@smol' },
|
|
63
|
+
level2: { rank: 2, model: '@default' },
|
|
64
|
+
level3: { rank: 3, model: '@slow' },
|
|
44
65
|
};
|
|
45
66
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
'
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
67
|
+
// Verified against tagged v17.2.1 source: docs/environment-variables.md ("Model/provider
|
|
68
|
+
// authentication", "GitHub/Copilot tokens", "Auth broker", web-search credentials, Bedrock,
|
|
69
|
+
// Azure, Vertex sections) plus docs/providers.md's provider/env-var map. This is the full
|
|
70
|
+
// official host credential inventory OMP itself resolves credentials from — distinct from
|
|
71
|
+
// (and broader than) any narrower automatic Docker env-passthrough allowlist — because OMP
|
|
72
|
+
// routes to 70+ downstream model/search providers and every one of their credential env vars
|
|
73
|
+
// must be detected and redacted from Zeroshot logs/output, not just the handful Zeroshot's
|
|
74
|
+
// own cluster config commonly sets.
|
|
75
|
+
const CREDENTIAL_ENV_KEYS: readonly string[] = [
|
|
76
|
+
'AIMLAPI_API_KEY',
|
|
77
|
+
'AI_GATEWAY_API_KEY',
|
|
78
|
+
'ALIBABA_CODING_PLAN_API_KEY',
|
|
79
|
+
'ALIBABA_TOKEN_PLAN_API_KEY',
|
|
80
|
+
'ANTHROPIC_API_KEY',
|
|
81
|
+
'ANTHROPIC_FOUNDRY_API_KEY',
|
|
82
|
+
'ANTHROPIC_OAUTH_TOKEN',
|
|
83
|
+
'ANTHROPIC_SEARCH_API_KEY',
|
|
84
|
+
'AWS_ACCESS_KEY_ID',
|
|
85
|
+
'AWS_BEARER_TOKEN_BEDROCK',
|
|
86
|
+
'AWS_SECRET_ACCESS_KEY',
|
|
87
|
+
'AZURE_OPENAI_API_KEY',
|
|
88
|
+
'BAILIAN_TOKEN_PLAN_API_KEY',
|
|
89
|
+
'BRAVE_API_KEY',
|
|
90
|
+
'CEREBRAS_API_KEY',
|
|
91
|
+
'CLAUDE_CODE_CLIENT_CERT',
|
|
92
|
+
'CLAUDE_CODE_CLIENT_KEY',
|
|
93
|
+
'CLOUDFLARE_AI_GATEWAY_API_KEY',
|
|
94
|
+
'COPILOT_GITHUB_TOKEN',
|
|
95
|
+
'CURSOR_ACCESS_TOKEN',
|
|
96
|
+
'DEEPSEEK_API_KEY',
|
|
97
|
+
'EXA_API_KEY',
|
|
98
|
+
'FIREPASS_API_KEY',
|
|
99
|
+
'FIREWORKS_API_KEY',
|
|
100
|
+
'GEMINI_API_KEY',
|
|
101
|
+
'GH_TOKEN',
|
|
102
|
+
'GITHUB_TOKEN',
|
|
103
|
+
'GITLAB_TOKEN',
|
|
104
|
+
'GOOGLE_API_KEY',
|
|
105
|
+
'GOOGLE_APPLICATION_CREDENTIALS',
|
|
106
|
+
'GOOGLE_CLOUD_API_KEY',
|
|
107
|
+
'GROQ_API_KEY',
|
|
108
|
+
'HF_TOKEN',
|
|
109
|
+
'HUGGINGFACE_HUB_TOKEN',
|
|
110
|
+
'JINA_API_KEY',
|
|
111
|
+
'KAGI_API_KEY',
|
|
112
|
+
'KILO_API_KEY',
|
|
113
|
+
'KIMI_SEARCH_API_KEY',
|
|
114
|
+
'LITELLM_API_KEY',
|
|
115
|
+
'LLAMA_CPP_API_KEY',
|
|
116
|
+
'LM_STUDIO_API_KEY',
|
|
117
|
+
'MINIMAX_API_KEY',
|
|
118
|
+
'MINIMAX_CODE_API_KEY',
|
|
119
|
+
'MINIMAX_CODE_CN_API_KEY',
|
|
120
|
+
'MISTRAL_API_KEY',
|
|
121
|
+
'MOONSHOT_API_KEY',
|
|
122
|
+
'MOONSHOT_SEARCH_API_KEY',
|
|
123
|
+
'NANO_GPT_API_KEY',
|
|
124
|
+
'NOVITA_API_KEY',
|
|
125
|
+
'NVIDIA_API_KEY',
|
|
126
|
+
'OLLAMA_API_KEY',
|
|
127
|
+
'OLLAMA_CLOUD_API_KEY',
|
|
128
|
+
'OMP_AUTH_BROKER_TOKEN',
|
|
129
|
+
'OPENAI_API_KEY',
|
|
130
|
+
'OPENCODE_API_KEY',
|
|
131
|
+
'OPENROUTER_API_KEY',
|
|
132
|
+
'PARALLEL_API_KEY',
|
|
133
|
+
'PERPLEXITY_API_KEY',
|
|
134
|
+
'PERPLEXITY_COOKIES',
|
|
135
|
+
'QIANFAN_API_KEY',
|
|
136
|
+
'QWEN_OAUTH_TOKEN',
|
|
137
|
+
'QWEN_PORTAL_API_KEY',
|
|
138
|
+
'SEARXNG_BASIC_PASSWORD',
|
|
139
|
+
'SEARXNG_TOKEN',
|
|
140
|
+
'SILICONFLOW_API_KEY',
|
|
141
|
+
'SILICONFLOW_CN_API_KEY',
|
|
142
|
+
'SMITHERY_API_KEY',
|
|
143
|
+
'SYNTHETIC_API_KEY',
|
|
144
|
+
'TAVILY_API_KEY',
|
|
145
|
+
'TOGETHER_API_KEY',
|
|
146
|
+
'UMANS_AI_CODING_PLAN_API_KEY',
|
|
147
|
+
'VENICE_API_KEY',
|
|
148
|
+
'VLLM_API_KEY',
|
|
149
|
+
'WAFER_SERVERLESS_API_KEY',
|
|
150
|
+
'XAI_API_KEY',
|
|
151
|
+
'XAI_OAUTH_TOKEN',
|
|
152
|
+
'XIAOMI_API_KEY',
|
|
153
|
+
'XIAOMI_TOKEN_PLAN_AMS_API_KEY',
|
|
154
|
+
'XIAOMI_TOKEN_PLAN_CN_API_KEY',
|
|
155
|
+
'XIAOMI_TOKEN_PLAN_SGP_API_KEY',
|
|
156
|
+
'ZAI_API_KEY',
|
|
157
|
+
'ZENMUX_API_KEY',
|
|
158
|
+
'ZHIPU_API_KEY',
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
const VERSION_TOKEN_PATTERN = /(?<![\w.])17\.2\.1(?![\w.])/;
|
|
162
|
+
const MODEL_ID_PATTERN = /^@?[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/u;
|
|
163
|
+
|
|
164
|
+
function detectCliFeatures(helpText?: string | null, versionText?: string | null): OmpCliFeatures {
|
|
59
165
|
const help = helpText ?? '';
|
|
60
|
-
const
|
|
166
|
+
const version = (versionText ?? '').trim();
|
|
61
167
|
return {
|
|
62
168
|
provider: 'omp',
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
unknown,
|
|
169
|
+
versionMatches: VERSION_TOKEN_PATTERN.test(version),
|
|
170
|
+
supportsRpcMode: /(^|\s)rpc(\s|$)/m.test(help),
|
|
171
|
+
supportsConfig: /--config\b/.test(help),
|
|
172
|
+
supportsModel: /--model\b/.test(help),
|
|
173
|
+
supportsThinking: /--thinking\b/.test(help),
|
|
174
|
+
supportsApprovalMode: /--approval-mode\b/.test(help),
|
|
175
|
+
supportsNoTitle: /--no-title\b/.test(help),
|
|
176
|
+
supportsNoSession: /--no-session\b/.test(help),
|
|
177
|
+
supportsSessionDir: /--session-dir\b/.test(help),
|
|
178
|
+
supportsResume: /--resume\b/.test(help),
|
|
179
|
+
unknown: !help,
|
|
74
180
|
};
|
|
75
181
|
}
|
|
76
182
|
|
|
77
183
|
function assertRequiredOmpFeatures(options: BuildProviderCommandOptions): void {
|
|
78
184
|
const features = optionFeatures(options);
|
|
79
185
|
const required: ReadonlyArray<readonly [boolean | undefined, string]> = [
|
|
80
|
-
[features.
|
|
81
|
-
[features.
|
|
82
|
-
[features.
|
|
83
|
-
[features.
|
|
186
|
+
[features.versionMatches, `exact OMP version ${OMP_SUPPORTED_VERSION}`],
|
|
187
|
+
[features.supportsRpcMode, '"rpc" mode'],
|
|
188
|
+
[features.supportsConfig, '--config'],
|
|
189
|
+
[features.supportsModel, '--model'],
|
|
190
|
+
[features.supportsApprovalMode, '--approval-mode'],
|
|
191
|
+
[features.supportsNoTitle, '--no-title'],
|
|
192
|
+
[features.supportsNoSession, '--no-session'],
|
|
84
193
|
];
|
|
85
|
-
const missing = required.filter(([supported]) => supported === false).map(([,
|
|
194
|
+
const missing = required.filter(([supported]) => supported === false).map(([, label]) => label);
|
|
86
195
|
if (missing.length === 0) return;
|
|
87
196
|
throw contractError({
|
|
88
197
|
code: 'unsupported-provider-cli',
|
|
89
198
|
exitCode: 2,
|
|
90
199
|
message:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
'. Update/install @oh-my-pi/pi-coding-agent; Zeroshot will not silently fall back to Pi or another provider.',
|
|
200
|
+
`omp CLI is missing required evidence: ${missing.join(', ')}. ${OMP_REMEDIATION} ` +
|
|
201
|
+
'Zeroshot will not silently fall back to Pi or another provider.',
|
|
94
202
|
});
|
|
95
203
|
}
|
|
96
204
|
|
|
@@ -103,286 +211,78 @@ function failClosedUnsupportedSessionControl(options: BuildProviderCommandOption
|
|
|
103
211
|
field,
|
|
104
212
|
exitCode: 2,
|
|
105
213
|
message:
|
|
106
|
-
'OMP
|
|
214
|
+
'OMP RPC lane runs sessionless (--no-session) in this slice; resume/continue session control is capability-gated off (sessionResume: false).',
|
|
107
215
|
});
|
|
108
216
|
}
|
|
109
217
|
|
|
110
|
-
function
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
218
|
+
function rejectMcpConfig(options: BuildProviderCommandOptions): void {
|
|
219
|
+
if (!options.mcpConfig || options.mcpConfig.length === 0) return;
|
|
220
|
+
throw new UnsupportedProviderCapabilityError(
|
|
221
|
+
'omp',
|
|
222
|
+
'mcpServers',
|
|
223
|
+
"OMP does not accept Zeroshot's --mcp-config surface; OMP's own discovered MCP/web tools remain governed by its own harness policy, not translated from Claude/Copilot MCP envelopes."
|
|
224
|
+
);
|
|
116
225
|
}
|
|
117
226
|
|
|
118
|
-
function
|
|
119
|
-
const
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
227
|
+
function resolveModelSelector(options: BuildProviderCommandOptions): string {
|
|
228
|
+
const model = options.modelSpec?.model;
|
|
229
|
+
if (typeof model === 'string' && model.length > 0) return model;
|
|
230
|
+
throw contractError({
|
|
231
|
+
code: 'unsupported-provider-cli',
|
|
232
|
+
exitCode: 2,
|
|
233
|
+
message: 'omp requires a resolved --model selector; none was resolved for this run.',
|
|
234
|
+
});
|
|
126
235
|
}
|
|
127
236
|
|
|
128
237
|
function collectWarnings(options: BuildProviderCommandOptions): WarningMetadata[] {
|
|
129
|
-
|
|
130
|
-
|
|
238
|
+
if (!options.jsonSchema) return [];
|
|
239
|
+
return [
|
|
240
|
+
warning(
|
|
241
|
+
'omp',
|
|
242
|
+
'omp-jsonschema',
|
|
243
|
+
'OMP CLI does not support provider-native JSON schema; appending schema instructions to the prompt and validating locally.'
|
|
244
|
+
),
|
|
245
|
+
];
|
|
246
|
+
}
|
|
131
247
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
'omp',
|
|
136
|
-
'omp-jsonschema',
|
|
137
|
-
'OMP CLI does not support provider-native JSON schema; appending schema instructions to the prompt.'
|
|
138
|
-
)
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
if (options.modelSpec?.model && features.supportsModel === false) {
|
|
142
|
-
warnings.push(
|
|
143
|
-
warning(
|
|
144
|
-
'omp',
|
|
145
|
-
'omp-model-unsupported',
|
|
146
|
-
"OMP CLI does not advertise --model; continuing with OMP's own configured model."
|
|
147
|
-
)
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
if (options.modelSpec?.reasoningEffort && features.supportsThinking === false) {
|
|
151
|
-
warnings.push(
|
|
152
|
-
warning(
|
|
153
|
-
'omp',
|
|
154
|
-
'omp-thinking-unsupported',
|
|
155
|
-
'OMP CLI does not advertise --thinking; continuing without a reasoning-effort override.'
|
|
156
|
-
)
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
return warnings;
|
|
248
|
+
/** Prompt text sent over the RPC `prompt` command — never part of argv for the rpc-stdio lane. */
|
|
249
|
+
export function buildOmpPrompt(context: string, options: BuildProviderCommandOptions = {}): string {
|
|
250
|
+
return options.jsonSchema ? appendJsonSchemaPrompt(context, options.jsonSchema) : context;
|
|
160
251
|
}
|
|
161
252
|
|
|
162
|
-
function buildCommand(
|
|
253
|
+
function buildCommand(_context: string, options: BuildProviderCommandOptions = {}): CommandSpec {
|
|
163
254
|
assertRequiredOmpFeatures(options);
|
|
164
255
|
failClosedUnsupportedSessionControl(options);
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const
|
|
256
|
+
rejectMcpConfig(options);
|
|
257
|
+
const modelSelector = resolveModelSelector(options);
|
|
258
|
+
const warnings = collectWarnings(options);
|
|
259
|
+
const overlay = createOmpConfigOverlay();
|
|
169
260
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
args.push(
|
|
261
|
+
const args: string[] = ['--mode', 'rpc', '--no-session', '--model', modelSelector];
|
|
262
|
+
if (options.modelSpec?.reasoningEffort) {
|
|
263
|
+
args.push('--thinking', options.modelSpec.reasoningEffort);
|
|
264
|
+
}
|
|
265
|
+
args.push('--approval-mode', 'yolo', '--no-title', '--config', overlay.file);
|
|
175
266
|
|
|
176
267
|
return commandSpec({
|
|
177
268
|
binary: 'omp',
|
|
178
269
|
args,
|
|
179
270
|
env: {},
|
|
180
271
|
...(options.cwd === undefined ? {} : { cwd: options.cwd }),
|
|
181
|
-
|
|
272
|
+
cleanup: [overlay.dir],
|
|
273
|
+
cleanupMetadata: [
|
|
274
|
+
{ kind: 'temp-directory', provider: 'omp', path: overlay.dir, reason: 'isolated-config' },
|
|
275
|
+
],
|
|
276
|
+
warnings,
|
|
182
277
|
});
|
|
183
278
|
}
|
|
184
279
|
|
|
185
280
|
function createOmpState(): ProviderParserState {
|
|
186
|
-
return
|
|
187
|
-
...createParserState('omp'),
|
|
188
|
-
lastAssistantText: '',
|
|
189
|
-
lastAssistantThinking: '',
|
|
190
|
-
};
|
|
281
|
+
return createParserState('omp');
|
|
191
282
|
}
|
|
192
283
|
|
|
193
|
-
function
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
let text = '';
|
|
197
|
-
let thinking = '';
|
|
198
|
-
for (const item of getArray(message, 'content')) {
|
|
199
|
-
if (!isRecord(item)) continue;
|
|
200
|
-
const type = getString(item, 'type');
|
|
201
|
-
if (type === 'text') {
|
|
202
|
-
text += getString(item, 'text') ?? '';
|
|
203
|
-
} else if (type === 'thinking') {
|
|
204
|
-
thinking += getString(item, 'thinking') ?? '';
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return { text, thinking };
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function snapshotDelta(previous: string, current: string): string | null {
|
|
212
|
-
if (!current) return null;
|
|
213
|
-
if (previous === current) return null;
|
|
214
|
-
if (current.startsWith(previous)) return current.slice(previous.length) || null;
|
|
215
|
-
return current;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function emitAssistantSnapshot(
|
|
219
|
-
message: Record<string, unknown>,
|
|
220
|
-
state: ProviderParserState
|
|
221
|
-
): readonly OutputEvent[] {
|
|
222
|
-
const snapshot = assistantSnapshot(message);
|
|
223
|
-
const events: OutputEvent[] = [];
|
|
224
|
-
|
|
225
|
-
const textDelta = snapshotDelta(state.lastAssistantText ?? '', snapshot.text);
|
|
226
|
-
if (textDelta) events.push({ type: 'text', text: textDelta });
|
|
227
|
-
state.lastAssistantText = snapshot.text;
|
|
228
|
-
|
|
229
|
-
const thinkingDelta = snapshotDelta(state.lastAssistantThinking ?? '', snapshot.thinking);
|
|
230
|
-
if (thinkingDelta) events.push({ type: 'thinking', text: thinkingDelta });
|
|
231
|
-
state.lastAssistantThinking = snapshot.thinking;
|
|
232
|
-
|
|
233
|
-
return events;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function parseAssistantEvent(
|
|
237
|
-
event: Record<string, unknown>,
|
|
238
|
-
state: ProviderParserState
|
|
239
|
-
): OutputEvent | null {
|
|
240
|
-
const type = getString(event, 'type');
|
|
241
|
-
if (type === 'text_delta') {
|
|
242
|
-
const delta = getString(event, 'delta');
|
|
243
|
-
if (!delta) return null;
|
|
244
|
-
state.lastAssistantText += delta;
|
|
245
|
-
return { type: 'text', text: delta };
|
|
246
|
-
}
|
|
247
|
-
if (type === 'thinking_delta') {
|
|
248
|
-
const delta = getString(event, 'delta');
|
|
249
|
-
if (!delta) return null;
|
|
250
|
-
state.lastAssistantThinking += delta;
|
|
251
|
-
return { type: 'thinking', text: delta };
|
|
252
|
-
}
|
|
253
|
-
return null;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function parseToolExecutionStart(
|
|
257
|
-
event: Record<string, unknown>,
|
|
258
|
-
state: ProviderParserState
|
|
259
|
-
): OutputEvent {
|
|
260
|
-
const toolId = getOptionalString(event, 'toolCallId');
|
|
261
|
-
state.lastToolId = toolId;
|
|
262
|
-
return {
|
|
263
|
-
type: 'tool_call',
|
|
264
|
-
toolName: getOptionalString(event, 'toolName'),
|
|
265
|
-
toolId,
|
|
266
|
-
input: event.args ?? {},
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
function parseToolExecutionUpdate(
|
|
271
|
-
event: Record<string, unknown>,
|
|
272
|
-
state: ProviderParserState
|
|
273
|
-
): OutputEvent | null {
|
|
274
|
-
const toolId = getOptionalString(event, 'toolCallId') ?? state.lastToolId;
|
|
275
|
-
if (toolId !== undefined) state.lastToolId = toolId;
|
|
276
|
-
if (!Object.prototype.hasOwnProperty.call(event, 'partialResult')) return null;
|
|
277
|
-
return {
|
|
278
|
-
type: 'tool_result',
|
|
279
|
-
toolId,
|
|
280
|
-
content: event.partialResult,
|
|
281
|
-
isError: false,
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
function parseToolExecutionEnd(
|
|
286
|
-
event: Record<string, unknown>,
|
|
287
|
-
state: ProviderParserState
|
|
288
|
-
): OutputEvent {
|
|
289
|
-
const toolId = getOptionalString(event, 'toolCallId') ?? state.lastToolId;
|
|
290
|
-
return {
|
|
291
|
-
type: 'tool_result',
|
|
292
|
-
toolId,
|
|
293
|
-
content: event.result ?? '',
|
|
294
|
-
isError: getBoolean(event, 'isError') ?? false,
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function parseTurnEnd(
|
|
299
|
-
event: Record<string, unknown>,
|
|
300
|
-
state: ProviderParserState
|
|
301
|
-
): readonly OutputEvent[] {
|
|
302
|
-
const message = getRecord(event, 'message');
|
|
303
|
-
const events: OutputEvent[] = [];
|
|
304
|
-
if (message !== null) {
|
|
305
|
-
events.push(...emitAssistantSnapshot(message, state));
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const usage = message ? getRecord(message, 'usage') ?? {} : {};
|
|
309
|
-
const stopReason = message ? getString(message, 'stopReason') : null;
|
|
310
|
-
const errorMessage = message ? getString(message, 'errorMessage') : null;
|
|
311
|
-
const snapshot = message ? assistantSnapshot(message) : { text: '', thinking: '' };
|
|
312
|
-
const success = stopReason !== 'error' && stopReason !== 'aborted' && !errorMessage;
|
|
313
|
-
events.push({
|
|
314
|
-
type: 'result',
|
|
315
|
-
success,
|
|
316
|
-
result: success ? snapshot.text || null : null,
|
|
317
|
-
error: success ? null : (errorMessage ?? stopReason ?? 'OMP turn failed'),
|
|
318
|
-
inputTokens: getNumber(usage, 'input') ?? 0,
|
|
319
|
-
outputTokens: getNumber(usage, 'output') ?? 0,
|
|
320
|
-
cacheReadInputTokens: getNumber(usage, 'cacheRead') ?? 0,
|
|
321
|
-
cacheCreationInputTokens: getNumber(usage, 'cacheWrite') ?? 0,
|
|
322
|
-
cost: getRecord(usage, 'cost') ?? null,
|
|
323
|
-
modelUsage: usage,
|
|
324
|
-
});
|
|
325
|
-
return events;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
function parseMessageEvent(
|
|
329
|
-
type: string,
|
|
330
|
-
event: Record<string, unknown>,
|
|
331
|
-
state: ProviderParserState
|
|
332
|
-
): readonly OutputEvent[] | OutputEvent | null | undefined {
|
|
333
|
-
if (type === 'message_start') {
|
|
334
|
-
const message = getRecord(event, 'message');
|
|
335
|
-
if (message !== null && getString(message, 'role') === 'assistant') {
|
|
336
|
-
state.lastAssistantText = '';
|
|
337
|
-
state.lastAssistantThinking = '';
|
|
338
|
-
}
|
|
339
|
-
return null;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
if (type === 'message_update') {
|
|
343
|
-
const assistantMessageEvent = getRecord(event, 'assistantMessageEvent');
|
|
344
|
-
if (assistantMessageEvent !== null) {
|
|
345
|
-
const assistantEvent = parseAssistantEvent(assistantMessageEvent, state);
|
|
346
|
-
if (assistantEvent !== null) return assistantEvent;
|
|
347
|
-
}
|
|
348
|
-
const message = getRecord(event, 'message');
|
|
349
|
-
return message === null ? null : emitAssistantSnapshot(message, state);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (type !== 'message_end') return undefined;
|
|
353
|
-
const message = getRecord(event, 'message');
|
|
354
|
-
return message === null ? null : emitAssistantSnapshot(message, state);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function parseToolEvent(
|
|
358
|
-
type: string,
|
|
359
|
-
event: Record<string, unknown>,
|
|
360
|
-
state: ProviderParserState
|
|
361
|
-
): OutputEvent | null | undefined {
|
|
362
|
-
if (type === 'tool_execution_start') return parseToolExecutionStart(event, state);
|
|
363
|
-
if (type === 'tool_execution_update') return parseToolExecutionUpdate(event, state);
|
|
364
|
-
if (type === 'tool_execution_end') return parseToolExecutionEnd(event, state);
|
|
365
|
-
return undefined;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function parseEvent(
|
|
369
|
-
line: string,
|
|
370
|
-
state: ProviderParserState
|
|
371
|
-
): readonly OutputEvent[] | OutputEvent | null {
|
|
372
|
-
const parsed = tryParseJson(line);
|
|
373
|
-
if (!isRecord(parsed)) return null;
|
|
374
|
-
|
|
375
|
-
const type = getString(parsed, 'type');
|
|
376
|
-
if (type === null || IGNORED_EVENT_TYPES.has(type)) return null;
|
|
377
|
-
|
|
378
|
-
const messageEvent = parseMessageEvent(type, parsed, state);
|
|
379
|
-
if (messageEvent !== undefined) return messageEvent;
|
|
380
|
-
|
|
381
|
-
const toolEvent = parseToolEvent(type, parsed, state);
|
|
382
|
-
if (toolEvent !== undefined) return toolEvent;
|
|
383
|
-
|
|
384
|
-
if (type === 'turn_end') return parseTurnEnd(parsed, state);
|
|
385
|
-
return null;
|
|
284
|
+
function parseEvent(line: string, state: ProviderParserState): ProviderParseResult {
|
|
285
|
+
return parseNormalizedOmpRpcEventLine(line, state);
|
|
386
286
|
}
|
|
387
287
|
|
|
388
288
|
function resolveModelSpec(level: ModelLevel, overrides?: LevelOverrides): ResolvedModelSpec {
|
|
@@ -397,42 +297,89 @@ function resolveModelSpec(level: ModelLevel, overrides?: LevelOverrides): Resolv
|
|
|
397
297
|
|
|
398
298
|
function validateModelId(modelId: string | null | undefined): string | null | undefined {
|
|
399
299
|
if (modelId === undefined || modelId === null) return modelId;
|
|
400
|
-
if (typeof modelId !== 'string') {
|
|
401
|
-
throw new
|
|
300
|
+
if (typeof modelId !== 'string' || !MODEL_ID_PATTERN.test(modelId)) {
|
|
301
|
+
throw new InvalidProviderModelError(
|
|
302
|
+
`Invalid model "${unknownToMessage(modelId)}" for provider "omp": must match ${MODEL_ID_PATTERN.source}.`
|
|
303
|
+
);
|
|
402
304
|
}
|
|
403
305
|
return modelId;
|
|
404
306
|
}
|
|
405
307
|
|
|
308
|
+
const OMP_RETRYABLE_PATTERNS: readonly RegExp[] = [
|
|
309
|
+
/\brate(?:[_ -]?limit| limited)\b/i,
|
|
310
|
+
/\bquota\b/i,
|
|
311
|
+
/\bresource[_ -]?exhausted\b/i,
|
|
312
|
+
/\btemporar(?:y|ily)\b/i,
|
|
313
|
+
/\boverloaded\b/i,
|
|
314
|
+
/\bservice unavailable\b/i,
|
|
315
|
+
];
|
|
316
|
+
|
|
317
|
+
const OMP_PERMANENT_PATTERNS: readonly RegExp[] = [
|
|
318
|
+
// Driver stopReason tokens (see omp-rpc-driver.ts) that always indicate a permanent,
|
|
319
|
+
// non-retryable failure: bad binary/protocol/version/limits/selector/config, malformed or
|
|
320
|
+
// over-bound frames, and missing auth/model evidence.
|
|
321
|
+
/\bunsupported-protocol\b/i,
|
|
322
|
+
/\bunsupported-limits\b/i,
|
|
323
|
+
/\bunsupported-provider-cli\b/i,
|
|
324
|
+
/\bunsupported-ui-method\b/i,
|
|
325
|
+
/\bunsupported-capability\b/i,
|
|
326
|
+
/\bunsafe-config\b/i,
|
|
327
|
+
/\bmalformed-response\b/i,
|
|
328
|
+
/\bmalformed-extension-ui-request\b/i,
|
|
329
|
+
/\bmalformed-host-tool-call\b/i,
|
|
330
|
+
/\bmalformed-host-uri-request\b/i,
|
|
331
|
+
/\bextension-error\b/i,
|
|
332
|
+
/\blocal-only-prompt\b/i,
|
|
333
|
+
/\blifetime-request-id-exceeded\b/i,
|
|
334
|
+
/\bpending-request-exceeded\b/i,
|
|
335
|
+
/\bspawn-hook-failed\b/i,
|
|
336
|
+
// Frame decoder codes (omp-rpc-protocol.ts OmpRpcProtocolError.code values) — every one of
|
|
337
|
+
// these means the wire traffic itself is invalid or out-of-bounds, never a transient condition.
|
|
338
|
+
/\bphysical-frame-too-large\b/i,
|
|
339
|
+
/\bmalformed-physical-frame\b/i,
|
|
340
|
+
/\binvalid-chunk-metadata\b/i,
|
|
341
|
+
/\binvalid-chunk-data\b/i,
|
|
342
|
+
/\bchunk-sequence-must-start-at-zero\b/i,
|
|
343
|
+
/\bchunk-sequence-mismatch\b/i,
|
|
344
|
+
/\bchunk-sequence-exceeds-declared-length\b/i,
|
|
345
|
+
/\bchunk-sequence-length-mismatch\b/i,
|
|
346
|
+
/\bmalformed-json-in-reassembled-frame\b/i,
|
|
347
|
+
/\bnon-object-reassembled-frame\b/i,
|
|
348
|
+
/\binvalid-utf8-in-reassembled-frame\b/i,
|
|
349
|
+
/\binflight-reassembly-bytes-exceeded\b/i,
|
|
350
|
+
/\binterleaved-chunk-sequence\b/i,
|
|
351
|
+
/\binterrupted-chunk-sequence\b/i,
|
|
352
|
+
/\bincomplete-physical-frame\b/i,
|
|
353
|
+
/\bincomplete-chunk-sequence\b/i,
|
|
354
|
+
/\boutbound-frame-too-large\b/i,
|
|
355
|
+
/\bpre-negotiation-rpc-chunk\b/i,
|
|
356
|
+
/\b[\w-]+-bound-exceeded\b/i,
|
|
357
|
+
// Auth/model absence and permission/usage errors surfaced verbatim in OMP output.
|
|
358
|
+
/\bno available authenticated\b/i,
|
|
359
|
+
/\bno keyless model\b/i,
|
|
360
|
+
/\bno valid authentication\b/i,
|
|
361
|
+
/\bmissing api key\b/i,
|
|
362
|
+
/\brun\s*\/login\b/i,
|
|
363
|
+
/\bunknown option\b/i,
|
|
364
|
+
/\bfailed to load\b/i,
|
|
365
|
+
/\bcannot find module\b/i,
|
|
366
|
+
/\bno such file or directory\b/i,
|
|
367
|
+
];
|
|
368
|
+
|
|
406
369
|
function classifyError(error: unknown): ErrorClassification {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
/\bresource[_ -]?exhausted\b/i,
|
|
413
|
-
/\btemporar(?:y|ily)\b/i,
|
|
414
|
-
/\boverloaded\b/i,
|
|
415
|
-
/\bservice unavailable\b/i,
|
|
416
|
-
],
|
|
417
|
-
[
|
|
418
|
-
/\b(cancelled|canceled|aborted|interrupted)\b/i,
|
|
419
|
-
/\brun\s*\/login\b/i,
|
|
420
|
-
/\bmissing api key\b/i,
|
|
421
|
-
/\bno valid authentication\b/i,
|
|
422
|
-
/\bunknown option\b/i,
|
|
423
|
-
/\bfailed to load\b/i,
|
|
424
|
-
/\bcannot find module\b/i,
|
|
425
|
-
/\bno such file or directory\b/i,
|
|
426
|
-
]
|
|
427
|
-
);
|
|
370
|
+
const message = unknownToMessage(error);
|
|
371
|
+
if (/\b(cancelled|canceled)\b/i.test(message)) {
|
|
372
|
+
return { retryable: false, kind: 'cancelled' };
|
|
373
|
+
}
|
|
374
|
+
return classifyBaseProviderError(error, OMP_RETRYABLE_PATTERNS, OMP_PERMANENT_PATTERNS);
|
|
428
375
|
}
|
|
429
376
|
|
|
430
377
|
export const ompAdapter: ProviderAdapter = {
|
|
431
378
|
id: 'omp',
|
|
432
|
-
displayName: 'OMP',
|
|
379
|
+
displayName: 'OMP (Oh My Pi)',
|
|
433
380
|
binary: 'omp',
|
|
434
|
-
adapterVersion: '
|
|
435
|
-
credentialEnvKeys:
|
|
381
|
+
adapterVersion: '2',
|
|
382
|
+
credentialEnvKeys: CREDENTIAL_ENV_KEYS,
|
|
436
383
|
modelCatalog: MODEL_CATALOG,
|
|
437
384
|
levelMapping: LEVEL_MAPPING,
|
|
438
385
|
defaultLevel: 'level2',
|