@sema-agent/client-core 0.98.0 → 0.100.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +376 -0
- package/README.md +61 -35
- package/dist/adapt/arms.js +120 -55
- package/dist/adapt/panelTasks.d.ts +3 -2
- package/dist/adapt/panelTasks.js +34 -11
- package/dist/adapt/textStream.d.ts +1 -1
- package/dist/adapt/textStream.js +51 -28
- package/dist/adapter/activeRunSelfHeal.d.ts +11 -3
- package/dist/adapter/activeRunSelfHeal.js +210 -294
- package/dist/adapter/downstream/eventToSdkMessage.d.ts +25 -6
- package/dist/adapter/downstream/eventToSdkMessage.js +176 -12
- package/dist/adapter/downstream/terminalToSdkResult.js +10 -0
- package/dist/adapter/downstream/wiringManifestView.d.ts +3 -1
- package/dist/adapter/downstream/wiringManifestView.js +1 -0
- package/dist/adapter/runStream.js +82 -40
- package/dist/adapter/types.d.ts +2 -0
- package/dist/adapter/types.js +2 -1
- package/dist/agentSession/engineSessionBackgroundStop.js +16 -8
- package/dist/agentsWireCaps.d.ts +7 -3
- package/dist/agentsWireCaps.js +133 -39
- package/dist/cloudConfigWireCaps.d.ts +10 -1
- package/dist/cloudConfigWireCaps.js +181 -65
- package/dist/compactionStatus.d.ts +34 -0
- package/dist/compactionStatus.js +196 -0
- package/dist/controlRouter.d.ts +4 -2
- package/dist/controlRouter.js +39 -51
- package/dist/coreValuePorts.d.ts +26 -1
- package/dist/coreValuePorts.js +10 -0
- package/dist/corruptSessionCopy.d.ts +2 -1
- package/dist/corruptSessionCopy.js +3 -3
- package/dist/decideFailureNote.js +3 -3
- package/dist/declared/supportFloorDeclared.js +1 -1
- package/dist/detachWire.js +3 -2
- package/dist/devicesFace.d.ts +34 -0
- package/dist/devicesFace.js +90 -0
- package/dist/displayUntrusted.d.ts +1 -0
- package/dist/displayUntrusted.js +153 -5
- package/dist/engineAgentPanelStore.d.ts +14 -4
- package/dist/engineAgentPanelStore.js +14 -0
- package/dist/engineCapReader.d.ts +2 -1
- package/dist/engineCapReader.js +7 -5
- package/dist/engineCapsCache.d.ts +8 -0
- package/dist/engineCapsCache.js +59 -6
- package/dist/engineCapsChange.d.ts +2 -0
- package/dist/engineCapsChange.js +9 -0
- package/dist/engineErrorCodes.d.ts +11 -2
- package/dist/engineErrorCodes.js +11 -12
- package/dist/engineRowRef.js +4 -0
- package/dist/engineSessionMessages.d.ts +1 -0
- package/dist/engineSessionMessages.js +7 -1
- package/dist/engineTargetSeat.d.ts +27 -0
- package/dist/engineTargetSeat.js +199 -0
- package/dist/engineToolDirectory.d.ts +3 -0
- package/dist/engineToolDirectory.js +4 -2
- package/dist/engineWireFor.js +3 -8
- package/dist/env/uiLanguage.d.ts +2 -1
- package/dist/env/uiLanguage.js +3 -2
- package/dist/eventLeg.d.ts +7 -5
- package/dist/eventLeg.js +68 -49
- package/dist/fileHistoryCaptureCapability.d.ts +2 -4
- package/dist/fileHistoryCaptureCapability.js +12 -22
- package/dist/fileHistoryLegNotice.d.ts +2 -1
- package/dist/fileHistoryLegNotice.js +3 -3
- package/dist/frameBinding.d.ts +3 -0
- package/dist/frameBinding.js +12 -0
- package/dist/gateOutcome.js +16 -2
- package/dist/gateVocabulary.d.ts +8 -7
- package/dist/gateVocabulary.js +71 -73
- package/dist/generated/engineFactTables.d.ts +18 -1
- package/dist/generated/engineFactTables.js +56 -0
- package/dist/generated/engineNoticeTables.js +12 -3
- package/dist/generated/settingsSchemaFns.d.ts +18 -0
- package/dist/generated/settingsSchemaFns.js +70 -0
- package/dist/generated/settingsSchemaHooksClassifier.d.ts +2 -0
- package/dist/generated/settingsSchemaHooksClassifier.js +254 -0
- package/dist/generated/settingsSchemaMcpRequestEntry.d.ts +1 -0
- package/dist/generated/settingsSchemaMcpRequestEntry.js +162 -0
- package/dist/generated/settingsSchemaTables.d.ts +2 -0
- package/dist/generated/settingsSchemaTables.js +9 -0
- package/dist/generated/settingsSchemaTypes.d.ts +133 -0
- package/dist/generated/settingsSchemaTypes.js +1 -0
- package/dist/hitl/allowRuleShadow.d.ts +2 -1
- package/dist/hitl/allowRuleShadow.js +18 -15
- package/dist/hitl/approvalCardReasons.d.ts +3 -2
- package/dist/hitl/approvalCardReasons.js +29 -30
- package/dist/hitl/approvalHolds.d.ts +17 -0
- package/dist/hitl/approvalHolds.js +114 -29
- package/dist/hitl/askClassPair.d.ts +2 -2
- package/dist/hitl/askClassPair.js +16 -0
- package/dist/hitl/askGateWire.js +2 -1
- package/dist/hitl/askSettlementLine.d.ts +14 -4
- package/dist/hitl/askSettlementLine.js +67 -47
- package/dist/hitl/attributionLadder.d.ts +23 -0
- package/dist/hitl/attributionLadder.js +67 -0
- package/dist/hitl/automaticAnswerVerdict.d.ts +2 -1
- package/dist/hitl/automaticAnswerVerdict.js +8 -8
- package/dist/hitl/crashConvergedNotice.d.ts +5 -4
- package/dist/hitl/crashConvergedNotice.js +28 -41
- package/dist/hitl/decisionLedger.d.ts +55 -0
- package/dist/hitl/decisionLedger.js +156 -0
- package/dist/hitl/decisionRefusal.d.ts +1 -0
- package/dist/hitl/decisionRefusal.js +1 -1
- package/dist/hitl/decisionSend.d.ts +113 -0
- package/dist/hitl/decisionSend.js +579 -0
- package/dist/hitl/frameRouter.js +7 -8
- package/dist/hitl/hitlBridge.d.ts +1 -0
- package/dist/hitl/hitlBridge.js +21 -9
- package/dist/hitl/persistedRulesWire.d.ts +6 -5
- package/dist/hitl/persistedRulesWire.js +55 -73
- package/dist/hitl/planReviewWire.d.ts +5 -0
- package/dist/hitl/planReviewWire.js +53 -76
- package/dist/hitl/sessionPolicyDeliverable.d.ts +2 -0
- package/dist/hitl/sessionPolicyDeliverable.js +49 -9
- package/dist/hitl/sessionPolicyWire.d.ts +2 -1
- package/dist/hitl/sessionPolicyWire.js +23 -24
- package/dist/hitl/suspendedReopen.d.ts +2 -1
- package/dist/hitl/suspendedReopen.js +7 -10
- package/dist/hitl/toolApprovalWire.d.ts +17 -5
- package/dist/hitl/toolApprovalWire.js +120 -147
- package/dist/hooksWireCaps.js +145 -56
- package/dist/host.d.ts +21 -0
- package/dist/hostTurnSettings.d.ts +110 -0
- package/dist/hostTurnSettings.js +692 -0
- package/dist/humanTurn.d.ts +15 -1
- package/dist/humanTurn.js +100 -12
- package/dist/humanTurnRead.d.ts +2 -0
- package/dist/humanTurnRead.js +22 -1
- package/dist/i18n/messages.en.d.ts +713 -0
- package/dist/i18n/messages.en.js +713 -0
- package/dist/i18n/messages.zh.js +854 -1
- package/dist/index.d.ts +45 -13
- package/dist/index.js +27 -9
- package/dist/inheritEnvCapability.d.ts +4 -2
- package/dist/inheritEnvCapability.js +12 -5
- package/dist/inheritEnvWire.d.ts +3 -2
- package/dist/inheritEnvWire.js +14 -16
- package/dist/injectionLaneNotice.d.ts +5 -0
- package/dist/injectionLaneNotice.js +46 -0
- package/dist/interactiveHalt.js +17 -6
- package/dist/liveModelCatalog.d.ts +2 -1
- package/dist/liveModelCatalog.js +14 -8
- package/dist/mcpPanel.d.ts +10 -1
- package/dist/mcpPanel.js +42 -13
- package/dist/mcpProbeCapability.d.ts +2 -1
- package/dist/mcpProbeCapability.js +7 -11
- package/dist/mcpProbeWire.d.ts +3 -1
- package/dist/mcpProbeWire.js +5 -0
- package/dist/mcpReconnect.js +2 -2
- package/dist/mcpToolToggles.d.ts +6 -0
- package/dist/mcpToolToggles.js +46 -0
- package/dist/mcpWireCaps.d.ts +43 -3
- package/dist/mcpWireCaps.js +194 -59
- package/dist/mcpWireTransport.d.ts +4 -0
- package/dist/mcpWireTransport.js +18 -0
- package/dist/memoryEntriesWire.js +5 -10
- package/dist/model/onboardJudgement.d.ts +93 -0
- package/dist/model/onboardJudgement.js +415 -0
- package/dist/model/tierVocabulary.d.ts +2 -0
- package/dist/model/tierVocabulary.js +17 -5
- package/dist/modelCapabilityProbe.d.ts +65 -0
- package/dist/modelCapabilityProbe.js +601 -0
- package/dist/modelIdentityResolvability.d.ts +5 -0
- package/dist/modelIdentityResolvability.js +49 -22
- package/dist/ownKey.d.ts +33 -0
- package/dist/ownKey.js +109 -0
- package/dist/panelReduce.d.ts +11 -1
- package/dist/panelReduce.js +95 -16
- package/dist/panelStateHolder.d.ts +1 -0
- package/dist/panelStateHolder.js +10 -1
- package/dist/permissionRuleIssue.d.ts +2 -1
- package/dist/permissionRuleIssue.js +23 -20
- package/dist/permissionRulesWriteCapability.d.ts +2 -1
- package/dist/permissionRulesWriteCapability.js +8 -6
- package/dist/pluginHooksWire.d.ts +46 -3
- package/dist/pluginHooksWire.js +220 -12
- package/dist/prepareRefusal.d.ts +8 -0
- package/dist/prepareRefusal.js +72 -0
- package/dist/printModelStatus.d.ts +63 -0
- package/dist/printModelStatus.js +155 -0
- package/dist/request/taskRequest.d.ts +16 -10
- package/dist/request/taskRequest.js +195 -58
- package/dist/resumeRefusalCopy.d.ts +14 -9
- package/dist/resumeRefusalCopy.js +89 -144
- package/dist/retryStatus.d.ts +55 -0
- package/dist/retryStatus.js +155 -19
- package/dist/ruleRefusalSentence.d.ts +4 -0
- package/dist/ruleRefusalSentence.js +58 -0
- package/dist/sandboxWire.d.ts +2 -1
- package/dist/sandboxWire.js +55 -21
- package/dist/scenarioRefusal.d.ts +10 -0
- package/dist/scenarioRefusal.js +111 -0
- package/dist/seam.d.ts +48 -9
- package/dist/seam.js +54 -5
- package/dist/seatContract.d.ts +272 -14
- package/dist/seatContract.js +711 -23
- package/dist/selfOrchestrationDenial.js +7 -19
- package/dist/sessionIdSegment.d.ts +6 -0
- package/dist/sessionIdSegment.js +24 -0
- package/dist/sessionMemoryStatus.js +6 -11
- package/dist/sessionPolicyCapability.d.ts +2 -1
- package/dist/sessionPolicyCapability.js +8 -6
- package/dist/settingsCredentialKeyPaths.d.ts +8 -0
- package/dist/settingsCredentialKeyPaths.js +219 -0
- package/dist/settingsHooksSalvage.d.ts +44 -0
- package/dist/settingsHooksSalvage.js +211 -0
- package/dist/skillToolListsCapability.d.ts +2 -1
- package/dist/skillToolListsCapability.js +8 -6
- package/dist/skillsWireCaps.d.ts +0 -3
- package/dist/skillsWireCaps.js +1 -2
- package/dist/sseIdleTriage.js +3 -2
- package/dist/subagent/engineSubagentResume.js +15 -9
- package/dist/subagentContentStore.d.ts +4 -0
- package/dist/subagentContentStore.js +157 -27
- package/dist/toolHistoryMismatch.js +3 -2
- package/dist/toolRoster.d.ts +3 -2
- package/dist/toolRoster.js +21 -20
- package/dist/toolsFlagNames.d.ts +8 -0
- package/dist/toolsFlagNames.js +110 -0
- package/dist/turnFailurePresentation.d.ts +12 -1
- package/dist/turnFailurePresentation.js +220 -15
- package/dist/usageAnalytics.js +6 -11
- package/dist/userAttachmentEnvelope.d.ts +1 -0
- package/dist/userAttachmentEnvelope.js +37 -7
- package/dist/userAttachmentSeat.d.ts +21 -0
- package/dist/userAttachmentSeat.js +75 -0
- package/dist/userInvokedSkillsCapability.d.ts +11 -1
- package/dist/userInvokedSkillsCapability.js +31 -7
- package/dist/wireErrorTriage.d.ts +7 -0
- package/dist/wireErrorTriage.js +23 -1
- package/dist/wireFailureShape.js +2 -1
- package/dist/wireRefusalCopy.d.ts +62 -16
- package/dist/wireRefusalCopy.js +307 -148
- package/dist/workflowsJournalCapability.d.ts +2 -1
- package/dist/workflowsJournalCapability.js +8 -6
- package/docs/INTEGRATION-CLIENTS.md +951 -42
- package/package.json +5 -5
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { displayUntrusted } from './displayUntrusted.js';
|
|
2
|
+
import { abortableSleep } from './abortableSleep.js';
|
|
3
|
+
import { readThrownSlot } from './wireFailureShape.js';
|
|
4
|
+
import { guardedRead, readOwnDataValue } from './ownKey.js';
|
|
5
|
+
import { msg, renderMessage } from './i18n/translate.js';
|
|
1
6
|
export const THINKING_FORMATS = Object.freeze([
|
|
2
7
|
'openai',
|
|
3
8
|
'openrouter',
|
|
@@ -142,3 +147,599 @@ export function applyProbeToEntry(entry, result) {
|
|
|
142
147
|
}
|
|
143
148
|
return entry;
|
|
144
149
|
}
|
|
150
|
+
export const PROBE_VERDICTS = Object.freeze(['ok', 'auth', 'unreachable', 'model', 'shape', 'busy', 'no_token', 'other']);
|
|
151
|
+
function isBusyStatus(status) {
|
|
152
|
+
return status === 429 || status === 503 || status === 529;
|
|
153
|
+
}
|
|
154
|
+
function looksBusyError(apiError) {
|
|
155
|
+
return apiError !== undefined && /overload|rate.?limit|too many requests|too busy|server busy|capacity/i.test(apiError);
|
|
156
|
+
}
|
|
157
|
+
function apiErrorOf(parsed) {
|
|
158
|
+
const e = parsed?.error;
|
|
159
|
+
if (!e || typeof e !== 'object')
|
|
160
|
+
return undefined;
|
|
161
|
+
return [e.code ?? e.type, e.message].filter(Boolean).join(': ') || undefined;
|
|
162
|
+
}
|
|
163
|
+
function extractApiError(body) {
|
|
164
|
+
try {
|
|
165
|
+
return apiErrorOf(JSON.parse(body));
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function maskedByHost(raw, redact) {
|
|
172
|
+
if (redact === undefined)
|
|
173
|
+
return raw;
|
|
174
|
+
try {
|
|
175
|
+
const out = redact(raw);
|
|
176
|
+
return typeof out === 'string' ? out : undefined;
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function shownApiError(raw, redact) {
|
|
183
|
+
if (raw === undefined)
|
|
184
|
+
return undefined;
|
|
185
|
+
const masked = maskedByHost(raw, redact);
|
|
186
|
+
if (masked === undefined)
|
|
187
|
+
return undefined;
|
|
188
|
+
const shown = displayUntrusted(masked, { max: 200 });
|
|
189
|
+
return shown.trim() === '' ? undefined : shown;
|
|
190
|
+
}
|
|
191
|
+
function looksLikeModelError(status, apiError) {
|
|
192
|
+
if (status !== 400 && status !== 404 && status !== 422)
|
|
193
|
+
return false;
|
|
194
|
+
if (!apiError)
|
|
195
|
+
return status === 404;
|
|
196
|
+
return /model/i.test(apiError);
|
|
197
|
+
}
|
|
198
|
+
const MESSAGES_PATH = '/v1/messages';
|
|
199
|
+
const COMPLETIONS_PATH = '/chat/completions';
|
|
200
|
+
const MODELS_PATH_ANTHROPIC = '/v1/models';
|
|
201
|
+
const MODELS_PATH_OPENAI = '/models';
|
|
202
|
+
function verdictRequestBody(modelId) {
|
|
203
|
+
return JSON.stringify({ model: modelId ?? '', max_tokens: 1, messages: [{ role: 'user', content: 'hi' }], stream: true });
|
|
204
|
+
}
|
|
205
|
+
function judgeMessagesFailure(status, body, hasModelId, near, redact) {
|
|
206
|
+
if (status === 401 || status === 403)
|
|
207
|
+
return { verdict: 'auth', httpStatus: status, path: MESSAGES_PATH };
|
|
208
|
+
const apiError = extractApiError(body);
|
|
209
|
+
const shown = shownApiError(apiError, redact);
|
|
210
|
+
const ev = { httpStatus: status, path: MESSAGES_PATH, ...(shown !== undefined ? { apiError: shown } : {}) };
|
|
211
|
+
if (isBusyStatus(status) || looksBusyError(apiError))
|
|
212
|
+
return { verdict: 'busy', ...ev };
|
|
213
|
+
if (status === 404 && !apiError)
|
|
214
|
+
return { verdict: 'shape', httpStatus: status, path: MESSAGES_PATH };
|
|
215
|
+
if (looksLikeModelError(status, apiError)) {
|
|
216
|
+
if (!hasModelId)
|
|
217
|
+
return { verdict: 'ok', httpStatus: status, path: MESSAGES_PATH };
|
|
218
|
+
return { verdict: 'model', ...ev, ...(near !== undefined ? { nearModelId: near } : {}) };
|
|
219
|
+
}
|
|
220
|
+
if (!hasModelId && status >= 400 && status < 500 && apiError)
|
|
221
|
+
return { verdict: 'ok', httpStatus: status, path: MESSAGES_PATH };
|
|
222
|
+
return { verdict: 'other', ...ev };
|
|
223
|
+
}
|
|
224
|
+
function judgeCompletionsFailure(status, body, near, redact) {
|
|
225
|
+
if (status === 401 || status === 403)
|
|
226
|
+
return { verdict: 'auth', httpStatus: status, path: COMPLETIONS_PATH };
|
|
227
|
+
const apiError = extractApiError(body);
|
|
228
|
+
const shown = shownApiError(apiError, redact);
|
|
229
|
+
const ev = { httpStatus: status, path: COMPLETIONS_PATH, ...(shown !== undefined ? { apiError: shown } : {}) };
|
|
230
|
+
if (isBusyStatus(status) || looksBusyError(apiError))
|
|
231
|
+
return { verdict: 'busy', ...ev };
|
|
232
|
+
if (looksLikeModelError(status, apiError))
|
|
233
|
+
return { verdict: 'model', ...ev, ...(near !== undefined ? { nearModelId: near } : {}) };
|
|
234
|
+
return { verdict: 'other', ...ev };
|
|
235
|
+
}
|
|
236
|
+
function judgeAnswer(path, status, answer, waitedMs, redact) {
|
|
237
|
+
const at = { httpStatus: status, path };
|
|
238
|
+
switch (answer.kind) {
|
|
239
|
+
case 'token':
|
|
240
|
+
return { verdict: 'ok', ...at, waitedMs };
|
|
241
|
+
case 'no_token':
|
|
242
|
+
return { verdict: 'no_token', ...at, waitedMs };
|
|
243
|
+
case 'error': {
|
|
244
|
+
const shown = shownApiError(answer.apiError, redact);
|
|
245
|
+
return { verdict: looksBusyError(answer.apiError) ? 'busy' : 'shape', ...at, outcome: 'error', ...(shown !== undefined ? { apiError: shown } : {}) };
|
|
246
|
+
}
|
|
247
|
+
case 'not_model':
|
|
248
|
+
return { verdict: 'shape', ...at, outcome: 'not_model' };
|
|
249
|
+
case 'ended':
|
|
250
|
+
return { verdict: 'other', ...at, outcome: 'ended' };
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const ANSWER_SCAN_CAP = 1 << 20;
|
|
254
|
+
const SSE_FIELD_HEADS = ['data:', 'event:', 'id:', 'retry:'];
|
|
255
|
+
function nonEmptyText(v) {
|
|
256
|
+
return typeof v === 'string' && v !== '';
|
|
257
|
+
}
|
|
258
|
+
function positiveCount(v) {
|
|
259
|
+
return typeof v === 'number' && v > 0;
|
|
260
|
+
}
|
|
261
|
+
function anthropicBlockHasOutput(b) {
|
|
262
|
+
if (b === null || typeof b !== 'object')
|
|
263
|
+
return false;
|
|
264
|
+
const o = b;
|
|
265
|
+
return nonEmptyText(o.text) || nonEmptyText(o.thinking) || nonEmptyText(o.data) || o.type === 'tool_use';
|
|
266
|
+
}
|
|
267
|
+
function completionsChoiceHasOutput(c) {
|
|
268
|
+
if (!isChatChoiceShape(c))
|
|
269
|
+
return false;
|
|
270
|
+
const o = c;
|
|
271
|
+
for (const part of [o.delta, o.message]) {
|
|
272
|
+
if (part === null || typeof part !== 'object')
|
|
273
|
+
continue;
|
|
274
|
+
const d = part;
|
|
275
|
+
if (nonEmptyText(d.content) || nonEmptyText(d.reasoning_content) || nonEmptyText(d.reasoning))
|
|
276
|
+
return true;
|
|
277
|
+
if (Array.isArray(d.tool_calls) && d.tool_calls.length > 0)
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
return o.finish_reason === 'length';
|
|
281
|
+
}
|
|
282
|
+
function isTokenFrame(obj, api) {
|
|
283
|
+
if (api === 'anthropic-messages') {
|
|
284
|
+
if (obj.type === 'content_block_delta') {
|
|
285
|
+
const d = obj.delta;
|
|
286
|
+
return d !== null && d !== undefined && typeof d === 'object' && (nonEmptyText(d.text) || nonEmptyText(d.thinking) || nonEmptyText(d.partial_json));
|
|
287
|
+
}
|
|
288
|
+
if (obj.type === 'content_block_start')
|
|
289
|
+
return anthropicBlockHasOutput(obj.content_block);
|
|
290
|
+
if (obj.type === 'message_delta')
|
|
291
|
+
return obj.delta?.stop_reason === 'max_tokens';
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
if (Array.isArray(obj.choices) && obj.choices.some(completionsChoiceHasOutput))
|
|
295
|
+
return true;
|
|
296
|
+
return positiveCount(obj.usage?.completion_tokens);
|
|
297
|
+
}
|
|
298
|
+
function isChatChoiceShape(c) {
|
|
299
|
+
if (c === null || typeof c !== 'object')
|
|
300
|
+
return false;
|
|
301
|
+
const o = c;
|
|
302
|
+
return (o.delta !== null && typeof o.delta === 'object') || (o.message !== null && typeof o.message === 'object');
|
|
303
|
+
}
|
|
304
|
+
function isOwnLaneFrame(obj, api) {
|
|
305
|
+
if (api === 'anthropic-messages')
|
|
306
|
+
return typeof obj.type === 'string';
|
|
307
|
+
return Array.isArray(obj.choices) && (obj.choices.length === 0 || obj.choices.some(isChatChoiceShape));
|
|
308
|
+
}
|
|
309
|
+
function judgeJsonBody(text, contentType, api) {
|
|
310
|
+
let obj;
|
|
311
|
+
try {
|
|
312
|
+
obj = JSON.parse(text);
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
return { kind: 'not_model', contentType };
|
|
316
|
+
}
|
|
317
|
+
if (obj === null || typeof obj !== 'object')
|
|
318
|
+
return { kind: 'not_model', contentType };
|
|
319
|
+
const o = obj;
|
|
320
|
+
const apiError = apiErrorOf(o);
|
|
321
|
+
if (apiError !== undefined || o.type === 'error')
|
|
322
|
+
return { kind: 'error', ...(apiError !== undefined ? { apiError } : {}) };
|
|
323
|
+
const usage = (o.usage ?? null);
|
|
324
|
+
if (api === 'anthropic-messages') {
|
|
325
|
+
if (o.type !== 'message' && !Array.isArray(o.content))
|
|
326
|
+
return { kind: 'not_model', contentType };
|
|
327
|
+
const hasOutput = (Array.isArray(o.content) && o.content.some(anthropicBlockHasOutput)) || o.stop_reason === 'max_tokens' || positiveCount(usage?.output_tokens);
|
|
328
|
+
return hasOutput ? { kind: 'token' } : { kind: 'ended' };
|
|
329
|
+
}
|
|
330
|
+
if (Array.isArray(o.choices)) {
|
|
331
|
+
if (o.choices.length > 0 && !o.choices.some(isChatChoiceShape))
|
|
332
|
+
return { kind: 'not_model', contentType };
|
|
333
|
+
return o.choices.some(completionsChoiceHasOutput) || positiveCount(usage?.completion_tokens) ? { kind: 'token' } : { kind: 'ended' };
|
|
334
|
+
}
|
|
335
|
+
return { kind: 'not_model', contentType };
|
|
336
|
+
}
|
|
337
|
+
function createAnswerScanner(contentType, api) {
|
|
338
|
+
const ct = contentType.slice(0, 120);
|
|
339
|
+
let mode = /event-stream/i.test(ct) ? 'sse' : /json/i.test(ct) ? 'json' : undefined;
|
|
340
|
+
let buf = '';
|
|
341
|
+
let event = '';
|
|
342
|
+
let data = [];
|
|
343
|
+
let settled;
|
|
344
|
+
let sawOwn = false;
|
|
345
|
+
let sawForeign = false;
|
|
346
|
+
const dispatch = () => {
|
|
347
|
+
if (data.length === 0) {
|
|
348
|
+
event = '';
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const payload = data.join('\n');
|
|
352
|
+
const ev = event;
|
|
353
|
+
data = [];
|
|
354
|
+
event = '';
|
|
355
|
+
if (payload.trim() === '[DONE]')
|
|
356
|
+
return;
|
|
357
|
+
let obj;
|
|
358
|
+
try {
|
|
359
|
+
obj = JSON.parse(payload);
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
obj = undefined;
|
|
363
|
+
}
|
|
364
|
+
if (obj === null || typeof obj !== 'object')
|
|
365
|
+
return;
|
|
366
|
+
const o = obj;
|
|
367
|
+
const apiError = apiErrorOf(o);
|
|
368
|
+
if (ev === 'error' || o.type === 'error' || apiError !== undefined) {
|
|
369
|
+
settled = { kind: 'error', ...(apiError !== undefined ? { apiError } : {}) };
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (isTokenFrame(o, api))
|
|
373
|
+
settled = { kind: 'token' };
|
|
374
|
+
else if (isOwnLaneFrame(o, api))
|
|
375
|
+
sawOwn = true;
|
|
376
|
+
else
|
|
377
|
+
sawForeign = true;
|
|
378
|
+
};
|
|
379
|
+
const scanSse = () => {
|
|
380
|
+
let nl = buf.indexOf('\n');
|
|
381
|
+
while (nl !== -1 && settled === undefined) {
|
|
382
|
+
const line = buf.slice(0, nl).replace(/\r$/, '');
|
|
383
|
+
buf = buf.slice(nl + 1);
|
|
384
|
+
if (line === '')
|
|
385
|
+
dispatch();
|
|
386
|
+
else if (line.startsWith(':')) {
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
const colon = line.indexOf(':');
|
|
390
|
+
const field = colon === -1 ? line : line.slice(0, colon);
|
|
391
|
+
const value = colon === -1 ? '' : line.slice(colon + 1).replace(/^ /, '');
|
|
392
|
+
if (field === 'event')
|
|
393
|
+
event = value;
|
|
394
|
+
else if (field === 'data')
|
|
395
|
+
data.push(value);
|
|
396
|
+
}
|
|
397
|
+
nl = buf.indexOf('\n');
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
return {
|
|
401
|
+
push(chunk) {
|
|
402
|
+
if (settled !== undefined)
|
|
403
|
+
return settled;
|
|
404
|
+
buf += chunk;
|
|
405
|
+
if (mode === undefined) {
|
|
406
|
+
const head = buf.trimStart();
|
|
407
|
+
if (head === '')
|
|
408
|
+
return undefined;
|
|
409
|
+
const c = head[0];
|
|
410
|
+
if (c === '{' || c === '[')
|
|
411
|
+
mode = 'json';
|
|
412
|
+
else if (/^(data|event|id|retry):/.test(head) || c === ':')
|
|
413
|
+
mode = 'sse';
|
|
414
|
+
else if (SSE_FIELD_HEADS.some((f) => f.startsWith(head)))
|
|
415
|
+
return undefined;
|
|
416
|
+
else
|
|
417
|
+
return (settled = { kind: 'not_model', contentType: ct });
|
|
418
|
+
}
|
|
419
|
+
if (buf.length > ANSWER_SCAN_CAP)
|
|
420
|
+
return (settled = { kind: 'not_model', contentType: ct });
|
|
421
|
+
if (mode === 'sse')
|
|
422
|
+
scanSse();
|
|
423
|
+
return settled;
|
|
424
|
+
},
|
|
425
|
+
finish() {
|
|
426
|
+
if (settled !== undefined)
|
|
427
|
+
return settled;
|
|
428
|
+
if (mode === 'json')
|
|
429
|
+
return judgeJsonBody(buf, ct, api);
|
|
430
|
+
if (mode === 'sse') {
|
|
431
|
+
buf += '\n\n';
|
|
432
|
+
scanSse();
|
|
433
|
+
if (settled !== undefined)
|
|
434
|
+
return settled;
|
|
435
|
+
return sawForeign && !sawOwn ? { kind: 'not_model', contentType: ct } : { kind: 'ended' };
|
|
436
|
+
}
|
|
437
|
+
return buf.trim() === '' ? { kind: 'ended' } : { kind: 'not_model', contentType: ct };
|
|
438
|
+
},
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
function nearestListedId(body, modelId) {
|
|
442
|
+
const data = body?.data;
|
|
443
|
+
if (!Array.isArray(data))
|
|
444
|
+
return undefined;
|
|
445
|
+
const ids = data.map((x) => x?.id).filter((x) => typeof x === 'string');
|
|
446
|
+
if (ids.includes(modelId))
|
|
447
|
+
return undefined;
|
|
448
|
+
const norm = (x) => x.toLowerCase().replace(/[^a-z0-9]+/g, '');
|
|
449
|
+
const target = norm(modelId);
|
|
450
|
+
for (const len of [6, 5, 4]) {
|
|
451
|
+
const stem = target.slice(0, len);
|
|
452
|
+
if (stem.length < len)
|
|
453
|
+
continue;
|
|
454
|
+
const hit = ids.find((i) => norm(i).startsWith(stem));
|
|
455
|
+
if (hit !== undefined)
|
|
456
|
+
return hit;
|
|
457
|
+
}
|
|
458
|
+
return undefined;
|
|
459
|
+
}
|
|
460
|
+
function nearestFromCatalogText(text, modelId) {
|
|
461
|
+
try {
|
|
462
|
+
return nearestListedId(JSON.parse(text), modelId);
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
return undefined;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
function unwrapFetchCause(e) {
|
|
469
|
+
const parts = [];
|
|
470
|
+
let cur = e;
|
|
471
|
+
for (let i = 0; i < 4 && cur !== null && typeof cur === 'object'; i++) {
|
|
472
|
+
const code = readThrownSlot(cur, 'code');
|
|
473
|
+
const message = readThrownSlot(cur, 'message');
|
|
474
|
+
const c = typeof code === 'string' && code !== '' ? code : undefined;
|
|
475
|
+
const m = typeof message === 'string' && message !== '' ? message : undefined;
|
|
476
|
+
if (c !== undefined)
|
|
477
|
+
parts.push(m !== undefined && m !== c ? `${c} ${m}` : c);
|
|
478
|
+
else if (m !== undefined)
|
|
479
|
+
parts.push(m);
|
|
480
|
+
cur = readThrownSlot(cur, 'cause');
|
|
481
|
+
}
|
|
482
|
+
if (parts.length === 0 && typeof e === 'string')
|
|
483
|
+
parts.push(e);
|
|
484
|
+
return [...new Set(parts)].join(' ← ');
|
|
485
|
+
}
|
|
486
|
+
function shownCause(e, redact) {
|
|
487
|
+
const masked = maskedByHost(unwrapFetchCause(e), redact);
|
|
488
|
+
if (masked === undefined)
|
|
489
|
+
return undefined;
|
|
490
|
+
const shown = displayUntrusted(masked, { max: 160 });
|
|
491
|
+
return shown.trim() === '' ? undefined : shown;
|
|
492
|
+
}
|
|
493
|
+
const MAX_TIMER_MS = 2_147_483_647;
|
|
494
|
+
function probeDeadline(ms, cancel) {
|
|
495
|
+
const ctl = new AbortController();
|
|
496
|
+
const stop = new AbortController();
|
|
497
|
+
let expired = false;
|
|
498
|
+
const gone = new Promise((_resolve, reject) => {
|
|
499
|
+
const fire = () => reject(Object.assign(new Error('aborted'), { name: 'AbortError' }));
|
|
500
|
+
if (ctl.signal.aborted)
|
|
501
|
+
fire();
|
|
502
|
+
else
|
|
503
|
+
ctl.signal.addEventListener('abort', fire, { once: true });
|
|
504
|
+
});
|
|
505
|
+
gone.catch(() => undefined);
|
|
506
|
+
void abortableSleep(Math.min(ms, MAX_TIMER_MS), stop.signal).then(() => {
|
|
507
|
+
if (stop.signal.aborted)
|
|
508
|
+
return;
|
|
509
|
+
expired = true;
|
|
510
|
+
ctl.abort();
|
|
511
|
+
});
|
|
512
|
+
const onCancel = () => ctl.abort();
|
|
513
|
+
if (cancel !== undefined) {
|
|
514
|
+
if (cancel.aborted)
|
|
515
|
+
ctl.abort();
|
|
516
|
+
else
|
|
517
|
+
cancel.addEventListener('abort', onCancel, { once: true });
|
|
518
|
+
}
|
|
519
|
+
return {
|
|
520
|
+
signal: ctl.signal,
|
|
521
|
+
expired: () => expired,
|
|
522
|
+
abort: () => {
|
|
523
|
+
stop.abort();
|
|
524
|
+
cancel?.removeEventListener('abort', onCancel);
|
|
525
|
+
ctl.abort();
|
|
526
|
+
},
|
|
527
|
+
race: (p) => Promise.race([p, gone]),
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
async function getCatalogText(fetchIt, url, headers, timeoutMs, cancel) {
|
|
531
|
+
const d = probeDeadline(timeoutMs, cancel);
|
|
532
|
+
try {
|
|
533
|
+
const res = await d.race(fetchIt(url, { method: 'GET', headers, signal: d.signal }));
|
|
534
|
+
let text = '';
|
|
535
|
+
try {
|
|
536
|
+
text = await d.race(res.text());
|
|
537
|
+
}
|
|
538
|
+
catch {
|
|
539
|
+
text = '';
|
|
540
|
+
}
|
|
541
|
+
return { status: res.status, text };
|
|
542
|
+
}
|
|
543
|
+
finally {
|
|
544
|
+
d.abort();
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
async function postForFirstToken(fetchIt, api, url, path, headers, body, deadlineMs, cancel, now, redact) {
|
|
548
|
+
const d = probeDeadline(deadlineMs, cancel);
|
|
549
|
+
const startedAt = now();
|
|
550
|
+
try {
|
|
551
|
+
const res = await d.race(fetchIt(url, { method: 'POST', headers, body, signal: d.signal }));
|
|
552
|
+
if (res.status < 200 || res.status >= 300) {
|
|
553
|
+
let text = '';
|
|
554
|
+
try {
|
|
555
|
+
text = await d.race(res.text());
|
|
556
|
+
}
|
|
557
|
+
catch {
|
|
558
|
+
text = '';
|
|
559
|
+
}
|
|
560
|
+
return { kind: 'status', status: res.status, body: text };
|
|
561
|
+
}
|
|
562
|
+
const scanner = createAnswerScanner(res.headers.get('content-type') ?? '', api);
|
|
563
|
+
const pastDeadline = () => d.expired() || now() - startedAt >= deadlineMs;
|
|
564
|
+
const done = (raw) => {
|
|
565
|
+
const answer = raw.kind === 'token' && pastDeadline() ? { kind: 'no_token' } : raw;
|
|
566
|
+
return { kind: 'answer', verdict: judgeAnswer(path, res.status, answer, answer.kind === 'no_token' ? deadlineMs : now() - startedAt, redact) };
|
|
567
|
+
};
|
|
568
|
+
const stream = res.body;
|
|
569
|
+
if (stream === undefined || stream === null) {
|
|
570
|
+
let text = '';
|
|
571
|
+
try {
|
|
572
|
+
text = await d.race(res.text());
|
|
573
|
+
}
|
|
574
|
+
catch {
|
|
575
|
+
return done(d.signal.aborted ? { kind: 'no_token' } : scanner.finish());
|
|
576
|
+
}
|
|
577
|
+
if (pastDeadline())
|
|
578
|
+
return done({ kind: 'no_token' });
|
|
579
|
+
return done(scanner.push(text) ?? scanner.finish());
|
|
580
|
+
}
|
|
581
|
+
const reader = stream.getReader();
|
|
582
|
+
const decoder = new TextDecoder();
|
|
583
|
+
try {
|
|
584
|
+
for (;;) {
|
|
585
|
+
const { value, done: eof } = await d.race(reader.read());
|
|
586
|
+
if (pastDeadline())
|
|
587
|
+
return done({ kind: 'no_token' });
|
|
588
|
+
if (eof)
|
|
589
|
+
return done(scanner.push(decoder.decode()) ?? scanner.finish());
|
|
590
|
+
const settled = scanner.push(decoder.decode(value, { stream: true }));
|
|
591
|
+
if (settled !== undefined)
|
|
592
|
+
return done(settled);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
catch {
|
|
596
|
+
return done(d.signal.aborted ? { kind: 'no_token' } : scanner.finish());
|
|
597
|
+
}
|
|
598
|
+
finally {
|
|
599
|
+
reader.cancel().catch(() => undefined);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
finally {
|
|
603
|
+
d.abort();
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
export async function probeModelLiveness(input) {
|
|
607
|
+
try {
|
|
608
|
+
return await livenessOf(input);
|
|
609
|
+
}
|
|
610
|
+
catch (e) {
|
|
611
|
+
const cause = shownCause(e, redactOf(input));
|
|
612
|
+
return { verdict: 'unreachable', ...(cause !== undefined ? { cause } : {}) };
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
function redactOf(input) {
|
|
616
|
+
return typeof input.redact === 'function' ? input.redact : undefined;
|
|
617
|
+
}
|
|
618
|
+
async function livenessOf(input) {
|
|
619
|
+
const budget = input.firstTokenBudgetMs;
|
|
620
|
+
if (typeof budget !== 'number' || !Number.isFinite(budget) || budget <= 0)
|
|
621
|
+
return { verdict: 'unreachable', waitedMs: 0 };
|
|
622
|
+
const catalogBudget = typeof input.catalogBudgetMs === 'number' && Number.isFinite(input.catalogBudgetMs) && input.catalogBudgetMs > 0 ? input.catalogBudgetMs : budget;
|
|
623
|
+
const now = typeof input.now === 'function' ? input.now : Date.now;
|
|
624
|
+
const base = input.baseUrl.replace(/\/+$/, '');
|
|
625
|
+
const modelId = typeof input.modelId === 'string' && input.modelId.length > 0 ? input.modelId : undefined;
|
|
626
|
+
const anthropic = input.api === 'anthropic-messages';
|
|
627
|
+
const getHeaders = anthropic ? { 'anthropic-version': '2023-06-01' } : {};
|
|
628
|
+
const postHeaders = { ...getHeaders, 'content-type': 'application/json' };
|
|
629
|
+
const fetchIt = input.fetch;
|
|
630
|
+
const redact = redactOf(input);
|
|
631
|
+
const urlOf = (path) => {
|
|
632
|
+
if (typeof input.requestUrl !== 'function')
|
|
633
|
+
return `${base}${path}`;
|
|
634
|
+
const u = input.requestUrl(path);
|
|
635
|
+
if (typeof u !== 'string' || u === '')
|
|
636
|
+
throw new TypeError('request URL not built');
|
|
637
|
+
return u;
|
|
638
|
+
};
|
|
639
|
+
if (anthropic) {
|
|
640
|
+
const listCancel = new AbortController();
|
|
641
|
+
const nearP = modelId !== undefined
|
|
642
|
+
? getCatalogText(fetchIt, urlOf(MODELS_PATH_ANTHROPIC), getHeaders, catalogBudget, listCancel.signal)
|
|
643
|
+
.then((r) => (r.status >= 200 && r.status < 300 ? nearestFromCatalogText(r.text, modelId) : undefined))
|
|
644
|
+
.catch(() => undefined)
|
|
645
|
+
: Promise.resolve(undefined);
|
|
646
|
+
try {
|
|
647
|
+
let r;
|
|
648
|
+
try {
|
|
649
|
+
r = await postForFirstToken(fetchIt, input.api, urlOf(MESSAGES_PATH), MESSAGES_PATH, postHeaders, verdictRequestBody(modelId), budget, input.signal, now, redact);
|
|
650
|
+
}
|
|
651
|
+
catch (e) {
|
|
652
|
+
if (input.signal?.aborted !== true && isAbortLike(e))
|
|
653
|
+
return { verdict: 'unreachable', path: MESSAGES_PATH, waitedMs: budget };
|
|
654
|
+
const cause = shownCause(e, redact);
|
|
655
|
+
return { verdict: 'unreachable', path: MESSAGES_PATH, ...(cause !== undefined ? { cause } : {}) };
|
|
656
|
+
}
|
|
657
|
+
if (r.kind === 'answer')
|
|
658
|
+
return r.verdict;
|
|
659
|
+
const first = judgeMessagesFailure(r.status, r.body, modelId !== undefined, undefined, redact);
|
|
660
|
+
if (first.verdict !== 'model')
|
|
661
|
+
return first;
|
|
662
|
+
const near = await nearP;
|
|
663
|
+
return near === undefined ? first : judgeMessagesFailure(r.status, r.body, true, shownApiError(near, redact), redact);
|
|
664
|
+
}
|
|
665
|
+
finally {
|
|
666
|
+
listCancel.abort();
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
const gate = await getCatalogText(fetchIt, urlOf(MODELS_PATH_OPENAI), getHeaders, catalogBudget, input.signal);
|
|
670
|
+
if (gate.status === 401 || gate.status === 403)
|
|
671
|
+
return { verdict: 'auth', httpStatus: gate.status, path: '/models' };
|
|
672
|
+
if (isBusyStatus(gate.status))
|
|
673
|
+
return { verdict: 'busy', httpStatus: gate.status, path: '/models' };
|
|
674
|
+
if (gate.status < 200 || gate.status >= 300)
|
|
675
|
+
return { verdict: 'other', httpStatus: gate.status, path: '/models' };
|
|
676
|
+
const near = modelId !== undefined ? nearestFromCatalogText(gate.text, modelId) : undefined;
|
|
677
|
+
if (modelId === undefined)
|
|
678
|
+
return { verdict: 'ok', httpStatus: gate.status, path: '/models' };
|
|
679
|
+
try {
|
|
680
|
+
const r = await postForFirstToken(fetchIt, input.api, urlOf(COMPLETIONS_PATH), COMPLETIONS_PATH, postHeaders, verdictRequestBody(modelId), budget, input.signal, now, redact);
|
|
681
|
+
if (r.kind === 'answer')
|
|
682
|
+
return r.verdict;
|
|
683
|
+
return judgeCompletionsFailure(r.status, r.body, shownApiError(near, redact), redact);
|
|
684
|
+
}
|
|
685
|
+
catch (e) {
|
|
686
|
+
if (input.signal?.aborted !== true && isAbortLike(e))
|
|
687
|
+
return { verdict: 'unreachable', path: COMPLETIONS_PATH, waitedMs: budget };
|
|
688
|
+
const cause = shownCause(e, redact);
|
|
689
|
+
return { verdict: 'unreachable', path: COMPLETIONS_PATH, ...(cause !== undefined ? { cause } : {}) };
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
function isAbortLike(e) {
|
|
693
|
+
return readThrownSlot(e, 'name') === 'AbortError' || /abort/i.test(unwrapFetchCause(e));
|
|
694
|
+
}
|
|
695
|
+
function probeSeconds(ms) {
|
|
696
|
+
if (ms < 100)
|
|
697
|
+
return '<0.1s';
|
|
698
|
+
const sec = ms / 1000;
|
|
699
|
+
return `${Number.isInteger(sec) ? sec : sec.toFixed(1)}s`;
|
|
700
|
+
}
|
|
701
|
+
const readFacts = (o, key) => guardedRead(() => readOwnDataValue(o, key));
|
|
702
|
+
export function probeVerdictNotice(facts, ctx, lang) {
|
|
703
|
+
const rawWord = readFacts(facts, 'verdict');
|
|
704
|
+
const word = typeof rawWord === 'string' && PROBE_VERDICTS.includes(rawWord) ? rawWord : 'other';
|
|
705
|
+
const rawStatus = readFacts(facts, 'httpStatus');
|
|
706
|
+
const status = typeof rawStatus === 'number' && Number.isInteger(rawStatus) && rawStatus >= 100 && rawStatus <= 999 ? rawStatus : undefined;
|
|
707
|
+
const rawWaited = readFacts(facts, 'waitedMs');
|
|
708
|
+
const waited = typeof rawWaited === 'number' && Number.isFinite(rawWaited) && rawWaited >= 0 ? rawWaited : undefined;
|
|
709
|
+
const http = status === undefined ? '' : ` (HTTP ${status})`;
|
|
710
|
+
const rawModel = readFacts(ctx, 'modelName');
|
|
711
|
+
const model = typeof rawModel === 'string' && rawModel.trim() !== '' ? displayUntrusted(rawModel).trim() : '';
|
|
712
|
+
const prefix = model === '' ? '' : `${model}: `;
|
|
713
|
+
let line;
|
|
714
|
+
let recourse;
|
|
715
|
+
switch (word) {
|
|
716
|
+
case 'ok':
|
|
717
|
+
line = renderMessage(waited === undefined ? msg('model.probe.verdict.ok') : msg('model.probe.verdict.okWaited', { time: probeSeconds(waited) }), lang);
|
|
718
|
+
break;
|
|
719
|
+
case 'auth':
|
|
720
|
+
line = renderMessage(msg('model.probe.verdict.auth', { http }), lang);
|
|
721
|
+
recourse = 'check_credentials';
|
|
722
|
+
break;
|
|
723
|
+
case 'unreachable':
|
|
724
|
+
line = renderMessage(waited === undefined ? msg('model.probe.verdict.unreachable') : msg('model.probe.verdict.unreachableWaited', { time: probeSeconds(waited) }), lang);
|
|
725
|
+
break;
|
|
726
|
+
case 'model':
|
|
727
|
+
line = renderMessage(msg('model.probe.verdict.model', { http }), lang);
|
|
728
|
+
break;
|
|
729
|
+
case 'shape':
|
|
730
|
+
line = renderMessage(msg('model.probe.verdict.shape', { http }), lang);
|
|
731
|
+
break;
|
|
732
|
+
case 'busy':
|
|
733
|
+
line = renderMessage(msg('model.probe.verdict.busy', { http }), lang);
|
|
734
|
+
recourse = 'switch_model';
|
|
735
|
+
break;
|
|
736
|
+
case 'no_token':
|
|
737
|
+
line = renderMessage(waited === undefined ? msg('model.probe.verdict.noToken') : msg('model.probe.verdict.noTokenWaited', { time: probeSeconds(waited) }), lang);
|
|
738
|
+
recourse = 'switch_model';
|
|
739
|
+
break;
|
|
740
|
+
case 'other':
|
|
741
|
+
line = renderMessage(msg('model.probe.verdict.other', { http }), lang);
|
|
742
|
+
break;
|
|
743
|
+
}
|
|
744
|
+
return Object.freeze(recourse === undefined ? { line: `${prefix}${line}` } : { line: `${prefix}${line}`, recourse });
|
|
745
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { UiLanguage } from './env/uiLanguage.js';
|
|
1
2
|
type ModelIdentityLaneTuple = readonly [
|
|
2
3
|
'processEnv',
|
|
3
4
|
'settingsEnv',
|
|
@@ -11,11 +12,13 @@ export type ModelIdentityLanes = {
|
|
|
11
12
|
[L in ModelIdentityLane]: ModelIdentityLaneReading;
|
|
12
13
|
};
|
|
13
14
|
export type EngineAnswerObservation = 'answered' | 'not_observed' | 'unreachable';
|
|
15
|
+
export type EngineCatalogAdoptionReading = 'absent' | 'refused' | 'undetermined';
|
|
14
16
|
export interface LocalEngineModelIdentityReading {
|
|
15
17
|
engine: 'local';
|
|
16
18
|
lanes: ModelIdentityLanes;
|
|
17
19
|
engineAnswer?: EngineAnswerObservation;
|
|
18
20
|
engineAcceptsCatalogDefault?: boolean;
|
|
21
|
+
catalogAdoption?: EngineCatalogAdoptionReading;
|
|
19
22
|
routeGap?: ModelRouteGapReading;
|
|
20
23
|
}
|
|
21
24
|
export type ModelRouteGapReading = 'endpoint_missing' | 'lane_credential_missing' | 'none';
|
|
@@ -27,6 +30,8 @@ export type ModelIdentityReading = LocalEngineModelIdentityReading | RemoteEngin
|
|
|
27
30
|
export type ModelIdentityResolvability = 'resolvable' | 'not_resolvable' | 'unknown';
|
|
28
31
|
export type ModelSetupDecision = 'offer_setup' | 'offer_route_setup' | 'explain_unconfigured' | 'explain_route_gap' | 'explain_route_unconfigurable' | 'proceed';
|
|
29
32
|
export declare function modelIdentityResolvability(reading: ModelIdentityReading): ModelIdentityResolvability;
|
|
33
|
+
export declare function modelRouteGapSentence(lane: ModelIdentityLane, gap: ModelRouteGapReading, lang?: UiLanguage): string | undefined;
|
|
34
|
+
export declare function modelRouteGapFact(gap: ModelRouteGapReading, lang?: UiLanguage): string | undefined;
|
|
30
35
|
export declare function modelIdentityDetail(reading: ModelIdentityReading): string;
|
|
31
36
|
export declare function modelSetupDecision(reading: ModelIdentityReading, hostCanConfigureModel: boolean, hostCanConfigureRoute?: boolean): ModelSetupDecision;
|
|
32
37
|
export declare function modelSetupNotice(decision: ModelSetupDecision): string | null;
|