@sema-agent/client-core 0.82.6 → 0.83.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +11 -5
  3. package/dist/adapt/arms.d.ts +3 -2
  4. package/dist/adapt/arms.js +16 -11
  5. package/dist/adapt/ids.js +12 -8
  6. package/dist/adapt/textStream.d.ts +3 -2
  7. package/dist/adapt/toolCards.d.ts +2 -3
  8. package/dist/adapt/toolCards.js +1 -1
  9. package/dist/adapt.d.ts +1 -1
  10. package/dist/adapt.js +13 -6
  11. package/dist/adapter/activeRunSelfHeal.js +34 -17
  12. package/dist/adapter/downstream/eventToSdkMessage.js +1 -1
  13. package/dist/adapter/downstream/terminalToSdkResult.d.ts +2 -4
  14. package/dist/adapter/downstream/terminalToSdkResult.js +43 -5
  15. package/dist/adapter/runStream.js +6 -6
  16. package/dist/clientSlice.d.ts +0 -5
  17. package/dist/engineNoticeCodes.js +4 -0
  18. package/dist/fleet/fleetRowAgentType.d.ts +0 -1
  19. package/dist/fleet/fleetRowAgentType.js +0 -3
  20. package/dist/hitl/approvalResolution.js +2 -0
  21. package/dist/hitl/frameRouter.d.ts +3 -2
  22. package/dist/hitl/toolApprovalWire.d.ts +1 -0
  23. package/dist/hitl/toolApprovalWire.js +56 -10
  24. package/dist/hooksWireCaps.js +27 -1
  25. package/dist/host.d.ts +1 -0
  26. package/dist/liveInitToolFace.d.ts +4 -2
  27. package/dist/liveInitToolFace.js +2 -2
  28. package/dist/model/catalogLoader.d.ts +1 -6
  29. package/dist/panelRunningHistory.d.ts +3 -2
  30. package/dist/peerFrames.d.ts +0 -1
  31. package/dist/printInitToolFace.js +1 -1
  32. package/dist/request/printNotification.js +26 -26
  33. package/dist/seam.d.ts +8 -2
  34. package/dist/seam.js +18 -7
  35. package/dist/systemReminderTag.d.ts +0 -1
  36. package/dist/systemReminderTag.js +0 -3
  37. package/dist/toolResult.js +1 -0
  38. package/dist/uuidV5.d.ts +3 -0
  39. package/dist/uuidV5.js +178 -0
  40. package/dist/webSearchWireCaps.d.ts +20 -3
  41. package/dist/webSearchWireCaps.js +193 -25
  42. package/dist/wireFailureShape.d.ts +2 -1
  43. package/docs/INTEGRATION-CLIENTS.md +463 -12
  44. package/package.json +2 -2
package/dist/seam.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { TRANSCRIPT_ID_NAMESPACE, uuidV5 } from './uuidV5.js';
1
2
  const CHROME_ARM_TABLE = {
2
3
  retry_status: { required: false, duty: '渲/清 spinner 的重试覆盖层' },
3
4
  panel_task: { required: true, duty: 'publishEngineAgentPanelEvent(event) 原样转发' },
@@ -63,6 +64,13 @@ const CHROME_ARM_TABLE = {
63
64
  duty: 'CC-08(0.71.0):每个压缩边界一条,按**最后一条**取当前值;`usedTokens > compactAtTokens` 是引擎的 shouldCompact 谓词,别重算;`windowTokens` 是 autocompact 窗,' +
64
65
  '别渲成模型上下文大小;🔴 `sections[].tokens` 与 `usedTokens` 不同坐标系,**禁相减**、禁拿一段比 `compactAtTokens`;`sections` 整键缺席 = 没装配出段 IR,不是各段为零。',
65
66
  },
67
+ memory_saved: {
68
+ required: true,
69
+ duty: '0.83.0 起记忆写入不再是转录行,只发本臂。`notes` 是记忆**笔记原文、不是路径**(别拼链接 / 判文件存在);' +
70
+ '要保留「Saved N memories」那一行的宿主把本臂还原成一条转录行:行 uuid 用 `id`(由这次写入的 wire 稳定键派生、重投同值,按它幂等;与 0.82.x 旧行 uuid 不相等),N = `notes.length`,时间戳在接收口取;' +
71
+ '落成 CC 形行时不许再造 `writtenPaths` / `isMeta` 这类 CC 在该臂上不声明的键;每次写入一条事件、本包不合并;' +
72
+ '没收到本臂不等于没写记忆(回执不成形时不发,工具卡照常在);laneProof 为 subagent = 子代写的,别渲成主会话的。',
73
+ },
66
74
  compaction_outcome: {
67
75
  required: false,
68
76
  duty: 'CC-89(0.75.0):一次压缩的非 compacted 结局(作废 / 失败等),给用户一条可见提示。`outcome` / `trigger` 是引擎开集原词,按成员渲染必带 default 臂、认不得照原词显示;' +
@@ -139,12 +147,15 @@ const CHROME_ARM_TABLE = {
139
147
  },
140
148
  };
141
149
  export const CHROME_ARMS = Object.entries(CHROME_ARM_TABLE).map(([kind, d]) => ({ kind, required: d.required, duty: d.duty }));
142
- export function deriveTranscriptId(frame, ctx) {
143
- if (typeof frame.id === 'string' && frame.id.length > 0)
144
- return `wid_${frame.id}`;
145
- if (typeof frame.seq === 'number')
146
- return `wseq_${frame.seq}`;
147
- if (typeof frame.toolCallId === 'string' && frame.toolCallId.length > 0)
148
- return `wtc_${frame.toolCallId}`;
150
+ export function deriveTranscriptId(frame, ctx, suffix) {
151
+ if (typeof frame.id === 'string' && frame.id.length > 0) {
152
+ return uuidV5(TRANSCRIPT_ID_NAMESPACE, suffix !== undefined ? `wid_${frame.id}#${suffix}` : `wid_${frame.id}`);
153
+ }
154
+ if (typeof frame.seq === 'number') {
155
+ return uuidV5(TRANSCRIPT_ID_NAMESPACE, suffix !== undefined ? `wseq#${suffix}_${frame.seq}` : `wseq_${frame.seq}`);
156
+ }
157
+ if (typeof frame.toolCallId === 'string' && frame.toolCallId.length > 0) {
158
+ return uuidV5(TRANSCRIPT_ID_NAMESPACE, suffix !== undefined ? `wtc#${suffix}_${frame.toolCallId}` : `wtc_${frame.toolCallId}`);
159
+ }
149
160
  return ctx.uuid();
150
161
  }
@@ -1,3 +1,2 @@
1
1
  export declare function unwrapSystemReminder(text: string): string;
2
2
  export declare function stripSystemReminderBlocks(text: string): string;
3
- export declare function startsWithSystemReminderOpenTag(text: string): boolean;
@@ -7,6 +7,3 @@ export function unwrapSystemReminder(text) {
7
7
  export function stripSystemReminderBlocks(text) {
8
8
  return text.replace(new RegExp(`${OPEN_TAG_SOURCE}[\\s\\S]*?<\\/system-reminder>`, 'g'), '');
9
9
  }
10
- export function startsWithSystemReminderOpenTag(text) {
11
- return new RegExp(`^\\s*${OPEN_TAG_SOURCE}`).test(text);
12
- }
@@ -47,6 +47,7 @@ export const STRUCTURED_DETAIL_TYPES = new Set([
47
47
  'read_path_denied',
48
48
  'list-agents',
49
49
  'artifact',
50
+ 'read-inbox',
50
51
  ]);
51
52
  export function structuredDetailType(structured) {
52
53
  if (typeof structured !== 'object' || structured === null)
@@ -0,0 +1,3 @@
1
+ export declare const TRANSCRIPT_ID_NAMESPACE = "5935225a-0f39-4c72-b409-4893196112ab";
2
+ export declare function utf8Encode(s: string): Uint8Array;
3
+ export declare function uuidV5(namespace: string, name: string): string;
package/dist/uuidV5.js ADDED
@@ -0,0 +1,178 @@
1
+ export const TRANSCRIPT_ID_NAMESPACE = '5935225a-0f39-4c72-b409-4893196112ab';
2
+ const HEX = '0123456789abcdef';
3
+ export function utf8Encode(s) {
4
+ let n = 0;
5
+ for (let i = 0; i < s.length; i++) {
6
+ const c = s.charCodeAt(i);
7
+ if (c < 0x80)
8
+ n += 1;
9
+ else if (c < 0x800)
10
+ n += 2;
11
+ else if (c >= 0xd800 && c <= 0xdbff && i + 1 < s.length) {
12
+ const d = s.charCodeAt(i + 1);
13
+ if (d >= 0xdc00 && d <= 0xdfff) {
14
+ n += 4;
15
+ i++;
16
+ }
17
+ else
18
+ n += 3;
19
+ }
20
+ else
21
+ n += 3;
22
+ }
23
+ const out = new Uint8Array(n);
24
+ let p = 0;
25
+ for (let i = 0; i < s.length; i++) {
26
+ let c = s.charCodeAt(i);
27
+ if (c >= 0xd800 && c <= 0xdfff) {
28
+ const d = c <= 0xdbff && i + 1 < s.length ? s.charCodeAt(i + 1) : 0;
29
+ if (c <= 0xdbff && d >= 0xdc00 && d <= 0xdfff) {
30
+ c = 0x10000 + ((c - 0xd800) << 10) + (d - 0xdc00);
31
+ i++;
32
+ out[p++] = 0xf0 | (c >>> 18);
33
+ out[p++] = 0x80 | ((c >>> 12) & 0x3f);
34
+ out[p++] = 0x80 | ((c >>> 6) & 0x3f);
35
+ out[p++] = 0x80 | (c & 0x3f);
36
+ continue;
37
+ }
38
+ c = 0xfffd;
39
+ }
40
+ if (c < 0x80) {
41
+ out[p++] = c;
42
+ }
43
+ else if (c < 0x800) {
44
+ out[p++] = 0xc0 | (c >>> 6);
45
+ out[p++] = 0x80 | (c & 0x3f);
46
+ }
47
+ else {
48
+ out[p++] = 0xe0 | (c >>> 12);
49
+ out[p++] = 0x80 | ((c >>> 6) & 0x3f);
50
+ out[p++] = 0x80 | (c & 0x3f);
51
+ }
52
+ }
53
+ return out;
54
+ }
55
+ function sha1(msg) {
56
+ const len = msg.length;
57
+ const blocks = ((len + 8) >>> 6) + 1;
58
+ const buf = new Uint8Array(blocks * 64);
59
+ buf.set(msg);
60
+ buf[len] = 0x80;
61
+ const bitsHi = Math.floor(len / 0x20000000);
62
+ const bitsLo = (len << 3) >>> 0;
63
+ const end = buf.length;
64
+ buf[end - 8] = (bitsHi >>> 24) & 0xff;
65
+ buf[end - 7] = (bitsHi >>> 16) & 0xff;
66
+ buf[end - 6] = (bitsHi >>> 8) & 0xff;
67
+ buf[end - 5] = bitsHi & 0xff;
68
+ buf[end - 4] = (bitsLo >>> 24) & 0xff;
69
+ buf[end - 3] = (bitsLo >>> 16) & 0xff;
70
+ buf[end - 2] = (bitsLo >>> 8) & 0xff;
71
+ buf[end - 1] = bitsLo & 0xff;
72
+ let h0 = 0x67452301;
73
+ let h1 = 0xefcdab89;
74
+ let h2 = 0x98badcfe;
75
+ let h3 = 0x10325476;
76
+ let h4 = 0xc3d2e1f0;
77
+ const w = new Int32Array(80);
78
+ for (let off = 0; off < end; off += 64) {
79
+ for (let t = 0; t < 16; t++) {
80
+ const q = off + t * 4;
81
+ w[t] = ((buf[q] ?? 0) << 24) | ((buf[q + 1] ?? 0) << 16) | ((buf[q + 2] ?? 0) << 8) | (buf[q + 3] ?? 0);
82
+ }
83
+ for (let t = 16; t < 80; t++) {
84
+ const x = (w[t - 3] ?? 0) ^ (w[t - 8] ?? 0) ^ (w[t - 14] ?? 0) ^ (w[t - 16] ?? 0);
85
+ w[t] = (x << 1) | (x >>> 31);
86
+ }
87
+ let a = h0;
88
+ let b = h1;
89
+ let c = h2;
90
+ let d = h3;
91
+ let e = h4;
92
+ for (let t = 0; t < 80; t++) {
93
+ let f;
94
+ let k;
95
+ if (t < 20) {
96
+ f = (b & c) | (~b & d);
97
+ k = 0x5a827999;
98
+ }
99
+ else if (t < 40) {
100
+ f = b ^ c ^ d;
101
+ k = 0x6ed9eba1;
102
+ }
103
+ else if (t < 60) {
104
+ f = (b & c) | (b & d) | (c & d);
105
+ k = 0x8f1bbcdc;
106
+ }
107
+ else {
108
+ f = b ^ c ^ d;
109
+ k = 0xca62c1d6;
110
+ }
111
+ const tmp = (((a << 5) | (a >>> 27)) + f + e + k + (w[t] ?? 0)) | 0;
112
+ e = d;
113
+ d = c;
114
+ c = (b << 30) | (b >>> 2);
115
+ b = a;
116
+ a = tmp;
117
+ }
118
+ h0 = (h0 + a) | 0;
119
+ h1 = (h1 + b) | 0;
120
+ h2 = (h2 + c) | 0;
121
+ h3 = (h3 + d) | 0;
122
+ h4 = (h4 + e) | 0;
123
+ }
124
+ const out = new Uint8Array(20);
125
+ const hs = [h0, h1, h2, h3, h4];
126
+ for (let i = 0; i < 5; i++) {
127
+ const h = hs[i] ?? 0;
128
+ out[i * 4] = (h >>> 24) & 0xff;
129
+ out[i * 4 + 1] = (h >>> 16) & 0xff;
130
+ out[i * 4 + 2] = (h >>> 8) & 0xff;
131
+ out[i * 4 + 3] = h & 0xff;
132
+ }
133
+ return out;
134
+ }
135
+ function hexVal(ch) {
136
+ if (ch >= 0x30 && ch <= 0x39)
137
+ return ch - 0x30;
138
+ if (ch >= 0x61 && ch <= 0x66)
139
+ return ch - 0x61 + 10;
140
+ if (ch >= 0x41 && ch <= 0x46)
141
+ return ch - 0x41 + 10;
142
+ return -1;
143
+ }
144
+ export function uuidV5(namespace, name) {
145
+ if (namespace.length !== 36)
146
+ throw new TypeError('uuidV5: namespace must be a 36-character UUID string');
147
+ const ns = new Uint8Array(16);
148
+ let j = 0;
149
+ for (let i = 0; i < 36; i++) {
150
+ const ch = namespace.charCodeAt(i);
151
+ if (i === 8 || i === 13 || i === 18 || i === 23) {
152
+ if (ch !== 0x2d)
153
+ throw new TypeError('uuidV5: namespace must be a 36-character UUID string');
154
+ continue;
155
+ }
156
+ const hi = hexVal(ch);
157
+ const lo = hexVal(namespace.charCodeAt(i + 1));
158
+ if (hi < 0 || lo < 0)
159
+ throw new TypeError('uuidV5: namespace must be a 36-character UUID string');
160
+ ns[j++] = (hi << 4) | lo;
161
+ i++;
162
+ }
163
+ const nameBytes = utf8Encode(name);
164
+ const input = new Uint8Array(16 + nameBytes.length);
165
+ input.set(ns);
166
+ input.set(nameBytes, 16);
167
+ const h = sha1(input);
168
+ h[6] = ((h[6] ?? 0) & 0x0f) | 0x50;
169
+ h[8] = ((h[8] ?? 0) & 0x3f) | 0x80;
170
+ let s = '';
171
+ for (let i = 0; i < 16; i++) {
172
+ if (i === 4 || i === 6 || i === 8 || i === 10)
173
+ s += '-';
174
+ const b = h[i] ?? 0;
175
+ s += HEX.charAt(b >>> 4) + HEX.charAt(b & 0x0f);
176
+ }
177
+ return s;
178
+ }
@@ -5,6 +5,7 @@ export interface WebSearchSettings {
5
5
  apiKey?: string;
6
6
  endpoint?: string;
7
7
  maxResults?: number;
8
+ searxngParams?: Record<string, string>;
8
9
  }
9
10
  export declare const WEBSEARCH_ENV: {
10
11
  readonly provider: "SEMA_WEBSEARCH_PROVIDER";
@@ -12,6 +13,22 @@ export declare const WEBSEARCH_ENV: {
12
13
  readonly apiKey: "SEMA_WEBSEARCH_API_KEY";
13
14
  readonly maxResults: "SEMA_WEBSEARCH_MAX_RESULTS";
14
15
  };
15
- export declare function webSearchFromEnv(env?: EnvLike): WebSearchSettings | undefined;
16
- export declare function webSearchFromSettings(raw: unknown): WebSearchSettings | undefined;
17
- export declare function resolveWebSearch(env?: EnvLike, settingsRaw?: unknown): WebSearchSettings | undefined;
16
+ export type WebSearchSettingsField = 'section' | 'provider' | 'apiKey' | 'endpoint' | 'searxngParams' | 'maxResults';
17
+ export type WebSearchSettingsVerdict = {
18
+ kind: 'absent';
19
+ } | {
20
+ kind: 'honored';
21
+ source: 'env' | 'settings';
22
+ settings: WebSearchSettings;
23
+ } | {
24
+ kind: 'malformed';
25
+ source: 'env' | 'settings';
26
+ field: WebSearchSettingsField;
27
+ issue: string;
28
+ };
29
+ export type WebSearchApiKeyFor = (provider: WebSearchProvider, section: Readonly<Record<string, unknown>>) => string | undefined;
30
+ export declare function judgeWebSearchSettings(raw: unknown, apiKeyFor?: WebSearchApiKeyFor): WebSearchSettingsVerdict;
31
+ export declare function webSearchVerdictFromEnv(env?: EnvLike): WebSearchSettingsVerdict;
32
+ export declare function resolveWebSearchVerdict(env?: EnvLike, settingsRaw?: unknown, apiKeyFor?: WebSearchApiKeyFor): WebSearchSettingsVerdict;
33
+ export declare function webSearchVerdictMessage(verdict: WebSearchSettingsVerdict, context?: 'submit' | 'check'): string | undefined;
34
+ export declare function webSearchEndpointForDisplay(raw: string): string | undefined;
@@ -5,38 +5,206 @@ export const WEBSEARCH_ENV = {
5
5
  apiKey: 'SEMA_WEBSEARCH_API_KEY',
6
6
  maxResults: 'SEMA_WEBSEARCH_MAX_RESULTS',
7
7
  };
8
- function normProvider(v) {
9
- const p = typeof v === 'string' ? v.trim().toLowerCase() : '';
10
- return p === 'brave' || p === 'tavily' || p === 'searxng' ? p : undefined;
8
+ const isBlankText = (v) => v.trim() === '';
9
+ function isHierarchicalUrl(raw) {
10
+ try {
11
+ const u = new URL(raw);
12
+ return u.href.startsWith(u.protocol + '//');
13
+ }
14
+ catch {
15
+ return false;
16
+ }
11
17
  }
12
- function normMax(v) {
13
- const n = typeof v === 'number' ? v : Number(v);
14
- return Number.isFinite(n) && n > 0 ? Math.floor(n) : undefined;
18
+ function judgeProvider(raw) {
19
+ const word = typeof raw === 'string' ? raw.trim().toLowerCase() : undefined;
20
+ if (word === 'brave' || word === 'tavily' || word === 'searxng')
21
+ return { ok: true, value: word };
22
+ const shape = raw === undefined || raw === null ? 'it is missing' : typeof raw === 'string' ? 'it is not one of them' : 'it is not a string';
23
+ return {
24
+ ok: false,
25
+ issue: `must name one of brave | tavily | searxng (${shape}) — it names where this request's searches go, so an unknown or missing provider is refused rather than replaced by this deployment's own backend`,
26
+ };
27
+ }
28
+ function judgeText(raw) {
29
+ if (raw === undefined || raw === null)
30
+ return { ok: true, value: undefined };
31
+ if (typeof raw !== 'string')
32
+ return { ok: false, issue: 'must be a string' };
33
+ return { ok: true, value: isBlankText(raw) ? undefined : raw };
34
+ }
35
+ const ENDPOINT_ISSUE = 'must be an absolute URL with a scheme and a host, such as https://search.example.com (a bare host or host:port is not one)';
36
+ function judgeEndpoint(raw) {
37
+ if (raw === undefined || raw === null)
38
+ return { ok: true, value: undefined };
39
+ if (typeof raw !== 'string')
40
+ return { ok: false, issue: ENDPOINT_ISSUE };
41
+ if (isBlankText(raw))
42
+ return { ok: true, value: undefined };
43
+ if (!isHierarchicalUrl(raw))
44
+ return { ok: false, issue: ENDPOINT_ISSUE };
45
+ const u = new URL(raw);
46
+ return u.username !== '' || u.password !== ''
47
+ ? { ok: false, issue: 'must not carry userinfo (user or user:password before the host)' }
48
+ : { ok: true, value: raw };
15
49
  }
16
- function build(provider, apiKey, endpoint, maxResults) {
17
- const max = normMax(maxResults);
50
+ const COUNT_ISSUE = 'must be a number ≥ 1';
51
+ function judgeCount(raw) {
52
+ if (raw === undefined || raw === null)
53
+ return { ok: true, value: undefined };
54
+ return typeof raw === 'number' && Number.isFinite(raw) && raw >= 1 ? { ok: true, value: Math.floor(raw) } : { ok: false, issue: COUNT_ISSUE };
55
+ }
56
+ const SEARXNG_PARAMS_ISSUE = 'must be an object of parameter name → non-empty string, or a "name=value;name=value" string';
57
+ function judgeSearxngParams(raw) {
58
+ if (raw === undefined || raw === null)
59
+ return { ok: true, value: undefined };
60
+ const pairs = [];
61
+ if (typeof raw === 'string') {
62
+ const segments = raw.split(';');
63
+ for (let i = 0; i < segments.length; i++) {
64
+ const seg = segments[i];
65
+ if (isBlankText(seg))
66
+ continue;
67
+ const eq = seg.indexOf('=');
68
+ if (eq < 0)
69
+ return { ok: false, issue: `${SEARXNG_PARAMS_ISSUE} — entry #${i + 1} has no "="` };
70
+ pairs.push([seg.slice(0, eq).trim(), seg.slice(eq + 1).trim(), i + 1]);
71
+ }
72
+ }
73
+ else if (typeof raw === 'object' && !Array.isArray(raw)) {
74
+ const entries = Object.entries(raw);
75
+ for (let i = 0; i < entries.length; i++) {
76
+ const [k, v] = entries[i];
77
+ if (typeof v !== 'string')
78
+ return { ok: false, issue: `${SEARXNG_PARAMS_ISSUE} — entry #${i + 1} has a value that is not a string` };
79
+ pairs.push([k, v.trim(), i + 1]);
80
+ }
81
+ }
82
+ else {
83
+ return { ok: false, issue: `${SEARXNG_PARAMS_ISSUE} — got ${Array.isArray(raw) ? 'an array' : `a ${typeof raw}`}` };
84
+ }
85
+ const firstEntryOf = new Map();
86
+ for (const [k, v, n] of pairs) {
87
+ if (!/^[A-Za-z0-9_.-]+$/.test(k))
88
+ return { ok: false, issue: `${SEARXNG_PARAMS_ISSUE} — entry #${n} has a name that is not a parameter name (letters, digits, "_", "-", ".")` };
89
+ if (v === '')
90
+ return { ok: false, issue: `${SEARXNG_PARAMS_ISSUE} — entry #${n} has an empty value` };
91
+ const first = firstEntryOf.get(k);
92
+ if (first !== undefined)
93
+ return { ok: false, issue: `${SEARXNG_PARAMS_ISSUE} — entry #${n} repeats the name of entry #${first}` };
94
+ firstEntryOf.set(k, n);
95
+ }
96
+ return { ok: true, value: pairs.length === 0 ? undefined : Object.fromEntries(pairs.map(([k, v]) => [k, v])) };
97
+ }
98
+ function judgeSection(raw, source, apiKeyFor) {
99
+ if (raw === undefined || raw === null)
100
+ return { kind: 'absent' };
101
+ if (typeof raw !== 'object' || Array.isArray(raw)) {
102
+ return { kind: 'malformed', source, field: 'section', issue: 'must be an object { provider, apiKey?, endpoint?, searxngParams?, maxResults? }' };
103
+ }
104
+ const s = raw;
105
+ const provider = judgeProvider(s.provider);
106
+ if (!provider.ok)
107
+ return { kind: 'malformed', source, field: 'provider', issue: provider.issue };
108
+ let injected;
109
+ if (apiKeyFor !== undefined) {
110
+ try {
111
+ injected = apiKeyFor(provider.value, s);
112
+ }
113
+ catch {
114
+ injected = undefined;
115
+ }
116
+ }
117
+ const apiKey = typeof injected === 'string' && !isBlankText(injected) ? { ok: true, value: injected } : judgeText(s.apiKey);
118
+ if (!apiKey.ok)
119
+ return { kind: 'malformed', source, field: 'apiKey', issue: apiKey.issue };
120
+ const endpoint = judgeEndpoint(s.endpoint);
121
+ if (!endpoint.ok)
122
+ return { kind: 'malformed', source, field: 'endpoint', issue: endpoint.issue };
123
+ const searxngParams = judgeSearxngParams(s.searxngParams);
124
+ if (!searxngParams.ok)
125
+ return { kind: 'malformed', source, field: 'searxngParams', issue: searxngParams.issue };
126
+ const maxResults = judgeCount(s.maxResults);
127
+ if (!maxResults.ok)
128
+ return { kind: 'malformed', source, field: 'maxResults', issue: maxResults.issue };
18
129
  return {
19
- provider,
20
- ...(typeof apiKey === 'string' && apiKey ? { apiKey } : {}),
21
- ...(typeof endpoint === 'string' && endpoint ? { endpoint } : {}),
22
- ...(max ? { maxResults: max } : {}),
130
+ kind: 'honored',
131
+ source,
132
+ settings: {
133
+ provider: provider.value,
134
+ ...(apiKey.value !== undefined ? { apiKey: apiKey.value } : {}),
135
+ ...(endpoint.value !== undefined ? { endpoint: endpoint.value } : {}),
136
+ ...(searxngParams.value !== undefined ? { searxngParams: searxngParams.value } : {}),
137
+ ...(maxResults.value !== undefined ? { maxResults: maxResults.value } : {}),
138
+ },
23
139
  };
24
140
  }
25
- export function webSearchFromEnv(env = hostEnv()) {
26
- const provider = normProvider(env[WEBSEARCH_ENV.provider]);
27
- if (!provider)
141
+ export function judgeWebSearchSettings(raw, apiKeyFor) {
142
+ try {
143
+ return judgeSection(raw, 'settings', apiKeyFor);
144
+ }
145
+ catch {
146
+ return { kind: 'malformed', source: 'settings', field: 'section', issue: 'could not be read' };
147
+ }
148
+ }
149
+ export function webSearchVerdictFromEnv(env = hostEnv()) {
150
+ try {
151
+ const read = (k) => {
152
+ const v = env[k];
153
+ return typeof v === 'string' && !isBlankText(v) ? v : undefined;
154
+ };
155
+ const provider = read(WEBSEARCH_ENV.provider);
156
+ const apiKey = read(WEBSEARCH_ENV.apiKey);
157
+ const endpoint = read(WEBSEARCH_ENV.endpoint);
158
+ const maxRaw = read(WEBSEARCH_ENV.maxResults);
159
+ if (provider === undefined && apiKey === undefined && endpoint === undefined && maxRaw === undefined)
160
+ return { kind: 'absent' };
161
+ return judgeSection({
162
+ ...(provider !== undefined ? { provider } : {}),
163
+ ...(apiKey !== undefined ? { apiKey } : {}),
164
+ ...(endpoint !== undefined ? { endpoint } : {}),
165
+ ...(maxRaw !== undefined ? { maxResults: Number(maxRaw.trim()) } : {}),
166
+ }, 'env');
167
+ }
168
+ catch {
169
+ return { kind: 'malformed', source: 'env', field: 'section', issue: 'could not be read' };
170
+ }
171
+ }
172
+ export function resolveWebSearchVerdict(env = hostEnv(), settingsRaw, apiKeyFor) {
173
+ const fromEnv = webSearchVerdictFromEnv(env);
174
+ return fromEnv.kind === 'absent' ? judgeWebSearchSettings(settingsRaw, apiKeyFor) : fromEnv;
175
+ }
176
+ function envNameOfField(field) {
177
+ switch (field) {
178
+ case 'provider':
179
+ return WEBSEARCH_ENV.provider;
180
+ case 'apiKey':
181
+ return WEBSEARCH_ENV.apiKey;
182
+ case 'endpoint':
183
+ return WEBSEARCH_ENV.endpoint;
184
+ case 'maxResults':
185
+ return WEBSEARCH_ENV.maxResults;
186
+ case 'section':
187
+ case 'searxngParams':
188
+ return 'the SEMA_WEBSEARCH_* variables';
189
+ }
190
+ }
191
+ export function webSearchVerdictMessage(verdict, context = 'submit') {
192
+ if (verdict.kind !== 'malformed')
28
193
  return undefined;
29
- return build(provider, env[WEBSEARCH_ENV.apiKey], env[WEBSEARCH_ENV.endpoint], env[WEBSEARCH_ENV.maxResults]);
194
+ const where = verdict.source === 'env' ? envNameOfField(verdict.field) : verdict.field === 'section' ? 'the webSearch section in settings' : `webSearch.${verdict.field} in settings`;
195
+ const fix = verdict.source === 'env' ? 'fix it or unset the SEMA_WEBSEARCH_* variables' : 'fix it or remove the webSearch section';
196
+ return context === 'submit'
197
+ ? `Web search is misconfigured: ${where} ${verdict.issue}. The request was not sent — ${fix}.`
198
+ : `Web search is misconfigured: ${where} ${verdict.issue}. Requests will be refused until you ${fix}.`;
30
199
  }
31
- export function webSearchFromSettings(raw) {
32
- if (!raw || typeof raw !== 'object')
200
+ export function webSearchEndpointForDisplay(raw) {
201
+ if (typeof raw !== 'string' || !isHierarchicalUrl(raw))
33
202
  return undefined;
34
- const s = raw;
35
- const provider = normProvider(s.provider);
36
- if (!provider)
203
+ try {
204
+ const u = new URL(raw);
205
+ return u.protocol + '//' + u.host;
206
+ }
207
+ catch {
37
208
  return undefined;
38
- return build(provider, s.apiKey, s.endpoint, s.maxResults);
39
- }
40
- export function resolveWebSearch(env = hostEnv(), settingsRaw) {
41
- return webSearchFromEnv(env) ?? webSearchFromSettings(settingsRaw);
209
+ }
42
210
  }
@@ -1,4 +1,4 @@
1
- export interface WireFailureShape {
1
+ interface WireFailureShape {
2
2
  status?: number;
3
3
  errorCode?: string;
4
4
  retryAfterSec?: number;
@@ -9,3 +9,4 @@ export declare function readErrorNumber(o: object, key: string): number | undefi
9
9
  export declare function readErrorString(o: object, key: string): string | undefined;
10
10
  export declare function readErrorStringList(o: object, key: string): readonly string[] | undefined;
11
11
  export declare function wireFailureShapeOf(e: unknown): WireFailureShape;
12
+ export {};