dsh-web-search-enhanced 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -3
- package/lib/client.cjs +764 -555
- package/lib/client.cjs.map +1 -1
- package/lib/index.js +1592 -295
- package/package.json +3 -1
package/lib/index.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { WebError } from "@deepseek-ai/dsh-web";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
|
+
import { createTypertRemoteInfo } from "@deepseek-ai/dsh-typert-protocol/remote-info";
|
|
3
4
|
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
4
5
|
import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
|
|
5
6
|
import { Remote, RemoteError, TypertRemoteService, remoteErrorOf } from "@deepseek-ai/dsh-typert-protocol";
|
|
6
|
-
import { createTypertRemoteInfo } from "@deepseek-ai/dsh-typert-protocol/remote-info";
|
|
7
7
|
import { scopeOf } from "@deepseek-ai/dsh-scope";
|
|
8
8
|
import { defineDomain, domainTable } from "@deepseek-ai/dsh-storage-domain";
|
|
9
9
|
import { DEEPSEEK_DEFAULT_API_VERSION, DEEPSEEK_DEFAULT_BASE_URL, DEEPSEEK_DEFAULT_MAX_TOKENS, DEEPSEEK_DEFAULT_MAX_USES, DEEPSEEK_DEFAULT_MODEL, DeepSeekSearchProvider } from "@deepseek-ai/dsh-web-search-deepseek";
|
|
10
10
|
import { DEEPSEEK_DEFAULT_COUNT, DEEPSEEK_WEB_API_BASE_URL, DeepSeekSearchApiProvider } from "@deepseek-ai/dsh-web-deepseek-api";
|
|
11
|
+
import { WEB_SEARCH_MAX_QUERIES, WEB_SEARCH_MAX_RESULTS, formatSearchOutput, searchMetaFromValue } from "@deepseek-ai/dsh-tool-web";
|
|
12
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
11
13
|
//#region \0rolldown/runtime.js
|
|
12
14
|
var __defProp = Object.defineProperty;
|
|
13
15
|
var __exportAll = (all, no_symbols) => {
|
|
@@ -241,8 +243,8 @@ function runAdapter(io, wire, target, input, signal) {
|
|
|
241
243
|
async function adapterCredential(io, ref, signal) {
|
|
242
244
|
try {
|
|
243
245
|
return await io.errors.abortable(io.credential(ref), signal);
|
|
244
|
-
} catch
|
|
245
|
-
|
|
246
|
+
} catch {
|
|
247
|
+
io.errors.throwIfAborted(signal);
|
|
246
248
|
throw new Error("Adapter credential resolution failed");
|
|
247
249
|
}
|
|
248
250
|
}
|
|
@@ -14146,6 +14148,10 @@ const TEXT_MAX_LENGTH = 2e3;
|
|
|
14146
14148
|
const BUILTIN_PREFIX = "builtin:";
|
|
14147
14149
|
/** Operator-defined connection ids: `custom:` plus a short, readable name. */
|
|
14148
14150
|
const CUSTOM_CONNECTION_ID = /^custom:[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/u;
|
|
14151
|
+
/** Catalog connection ids: `builtin:` plus a lower-case, hyphenated catalog key. */
|
|
14152
|
+
const BUILTIN_CONNECTION_ID = /^builtin:[a-z0-9]{1,50}(?:-[a-z0-9]{1,50}){0,5}$/u;
|
|
14153
|
+
/** Whether a string, such as an untrusted tool argument, has the grammar of a connection id; it may still name nothing. */
|
|
14154
|
+
const isConnectionId = (value) => BUILTIN_CONNECTION_ID.test(value) || CUSTOM_CONNECTION_ID.test(value);
|
|
14149
14155
|
/** A DSH Credentials reference, the only form a key is ever stored in. */
|
|
14150
14156
|
const CREDENTIAL_REF = /^[A-Za-z_][A-Za-z0-9_]*$/u;
|
|
14151
14157
|
const builtinId = (key) => `${BUILTIN_PREFIX}${key}`;
|
|
@@ -14196,6 +14202,7 @@ const SETTINGS_FIELDS = [
|
|
|
14196
14202
|
"version",
|
|
14197
14203
|
"defaultConnection",
|
|
14198
14204
|
"showSelector",
|
|
14205
|
+
"enhancedTools",
|
|
14199
14206
|
"defaults",
|
|
14200
14207
|
"connections"
|
|
14201
14208
|
];
|
|
@@ -14238,16 +14245,25 @@ function migrateDocument(document, migrations = SETTINGS_MIGRATIONS) {
|
|
|
14238
14245
|
}
|
|
14239
14246
|
return result;
|
|
14240
14247
|
}
|
|
14241
|
-
|
|
14242
|
-
|
|
14243
|
-
|
|
14244
|
-
|
|
14248
|
+
/**
|
|
14249
|
+
* Operator text shown as one line (a label in menus, error messages and the
|
|
14250
|
+
* Agent's provider list): control characters and line or paragraph separators
|
|
14251
|
+
* become spaces, runs of whitespace one space, the ends trimmed.
|
|
14252
|
+
*/
|
|
14253
|
+
function singleLine(value) {
|
|
14254
|
+
return value.replaceAll(/[\p{Cc}\p{Zl}\p{Zp}]/gu, " ").replaceAll(/\s+/gu, " ").trim();
|
|
14255
|
+
}
|
|
14256
|
+
const text$3 = string$1().trim().min(1).max(TEXT_MAX_LENGTH);
|
|
14257
|
+
const label = string$1().transform(singleLine).pipe(string$1().min(1).max(200));
|
|
14258
|
+
const credentialRef$1 = text$3.regex(CREDENTIAL_REF, "must be a credential reference such as EXA_API_KEY");
|
|
14259
|
+
const endpoint = text$3.refine(isEndpoint, "must be an absolute http(s) URL without credentials, query or fragment");
|
|
14245
14260
|
const options = record(string$1(), unknown());
|
|
14246
14261
|
function documentSchema(defaults, currentVersion = 2) {
|
|
14247
14262
|
return strictObject({
|
|
14248
14263
|
version: literal(currentVersion).optional(),
|
|
14249
14264
|
defaultConnection: string$1().min(1).max(200).optional(),
|
|
14250
14265
|
showSelector: boolean$1().optional(),
|
|
14266
|
+
enhancedTools: boolean$1().optional(),
|
|
14251
14267
|
defaults: defaults.optional(),
|
|
14252
14268
|
connections: record(string$1().max(200), unknown()).refine((value) => Object.keys(value).length <= 200, "must contain at most 200 connections").optional()
|
|
14253
14269
|
});
|
|
@@ -14309,7 +14325,7 @@ function resolveDocument({ catalog, document, defaults, capabilityLabel, protoco
|
|
|
14309
14325
|
const customSchema = customConnectionSchema(adapterKeys, protocolKeys);
|
|
14310
14326
|
const connections = /* @__PURE__ */ new Map();
|
|
14311
14327
|
const rejectId = (id, path) => fail(path, "unknown connection id \"" + id + "\"; this release does not read 0.1.x settings; remove the field or use Reset on the settings page");
|
|
14312
|
-
const resolve = (id, key, override,
|
|
14328
|
+
const resolve = (id, key, override, origin) => {
|
|
14313
14329
|
const info = catalog[key];
|
|
14314
14330
|
const path = "connections." + id;
|
|
14315
14331
|
const access = info.kind === "model" ? "api-key" : override.access ?? defaultAccess(info.access);
|
|
@@ -14321,11 +14337,12 @@ function resolveDocument({ catalog, document, defaults, capabilityLabel, protoco
|
|
|
14321
14337
|
if (problems.length > 0) fail(path + ".options", problems.join("; ") + "; this release does not read 0.1.x settings; remove the field or use Reset on the settings page");
|
|
14322
14338
|
const trustedEndpoint = override.trustedEndpoint ?? false;
|
|
14323
14339
|
for (const [name, rule] of Object.entries(info.options)) if (rule.kind === "url" && values[name] !== void 0 && values[name] !== info.endpoint && !trustedEndpoint) fail(path + ".trustedEndpoint", "a non-official " + name + " needs trustedEndpoint: true");
|
|
14340
|
+
const disabled = override.disabled ?? (origin.kind === "builtin" ? info.optIn : false);
|
|
14324
14341
|
const base = {
|
|
14325
14342
|
id,
|
|
14326
14343
|
catalogKey: key,
|
|
14327
14344
|
label: override.label ?? info.label,
|
|
14328
|
-
disabled
|
|
14345
|
+
disabled,
|
|
14329
14346
|
remote: info.remote,
|
|
14330
14347
|
trustedEndpoint,
|
|
14331
14348
|
options: values
|
|
@@ -14342,7 +14359,7 @@ function resolveDocument({ catalog, document, defaults, capabilityLabel, protoco
|
|
|
14342
14359
|
};
|
|
14343
14360
|
if (info.kind !== "native" && info.kind !== "adapter") return fail(path + ".kind", "unsupported catalog kind");
|
|
14344
14361
|
if (!info.access.apiKey || info.endpoint === void 0) return fail(path, "remote catalog entry needs credentials and an endpoint");
|
|
14345
|
-
const url =
|
|
14362
|
+
const url = origin.kind === "custom" && origin.endpoint !== void 0 ? origin.endpoint : info.endpoint;
|
|
14346
14363
|
if (url !== info.endpoint && !trustedEndpoint) fail(path + ".trustedEndpoint", "a custom endpoint needs trustedEndpoint: true");
|
|
14347
14364
|
return {
|
|
14348
14365
|
...base,
|
|
@@ -14363,7 +14380,7 @@ function resolveDocument({ catalog, document, defaults, capabilityLabel, protoco
|
|
|
14363
14380
|
]) if (Object.hasOwn(raw, field)) fail("connections." + id + "." + field, "this connection takes its route from the Session model");
|
|
14364
14381
|
}
|
|
14365
14382
|
const override = raw === void 0 ? {} : parse(builtinOverrideSchema, raw, "connections." + id);
|
|
14366
|
-
connections.set(id, resolve(id, key, override));
|
|
14383
|
+
connections.set(id, resolve(id, key, override, { kind: "builtin" }));
|
|
14367
14384
|
}
|
|
14368
14385
|
for (const [id, raw] of Object.entries(overrides)) {
|
|
14369
14386
|
if (connections.has(id)) continue;
|
|
@@ -14373,7 +14390,10 @@ function resolveDocument({ catalog, document, defaults, capabilityLabel, protoco
|
|
|
14373
14390
|
if (custom.kind === "adapter") connections.set(id, resolve(id, custom.adapter, {
|
|
14374
14391
|
...custom,
|
|
14375
14392
|
access: "api-key"
|
|
14376
|
-
},
|
|
14393
|
+
}, {
|
|
14394
|
+
kind: "custom",
|
|
14395
|
+
endpoint: custom.endpoint
|
|
14396
|
+
}));
|
|
14377
14397
|
else {
|
|
14378
14398
|
const info = protocols[custom.protocol];
|
|
14379
14399
|
const values = custom.options ?? {};
|
|
@@ -14404,6 +14424,7 @@ function resolveDocument({ catalog, document, defaults, capabilityLabel, protoco
|
|
|
14404
14424
|
version: migrations.currentVersion,
|
|
14405
14425
|
defaultConnection,
|
|
14406
14426
|
showSelector: settings.showSelector ?? true,
|
|
14427
|
+
enhancedTools: settings.enhancedTools ?? false,
|
|
14407
14428
|
defaults: settings.defaults ?? parse(defaults.schema, {}, "defaults"),
|
|
14408
14429
|
connections
|
|
14409
14430
|
};
|
|
@@ -14650,175 +14671,17 @@ function toolJson(value) {
|
|
|
14650
14671
|
throw error;
|
|
14651
14672
|
}
|
|
14652
14673
|
}
|
|
14653
|
-
function nextModel(ctx, agent) {
|
|
14654
|
-
const pending = ctx.get("sessionProjections")?.stateOf(agent.session, "modelSelection")?.pending;
|
|
14655
|
-
if (pending !== void 0 && pending !== null) return complete(pending);
|
|
14656
|
-
const request = agent.session.requestHeader();
|
|
14657
|
-
return complete(request === void 0 ? agent.options : request.config);
|
|
14658
|
-
}
|
|
14659
|
-
function complete(value) {
|
|
14660
|
-
if (!value.provider || !value.model) return void 0;
|
|
14661
|
-
return {
|
|
14662
|
-
provider: value.provider,
|
|
14663
|
-
model: value.model
|
|
14664
|
-
};
|
|
14665
|
-
}
|
|
14666
|
-
/** The resolved route used for native account authentication; pending choices do not authenticate. */
|
|
14667
|
-
function requestModel(agent) {
|
|
14668
|
-
const request = agent.session.requestContext();
|
|
14669
|
-
return request === void 0 ? void 0 : complete(request);
|
|
14670
|
-
}
|
|
14671
|
-
//#endregion
|
|
14672
|
-
//#region ../core/src/host/credentials.ts
|
|
14673
|
-
/** Credentials is authoritative; absence does not fall back to process.env. */
|
|
14674
|
-
async function resolveApiKey(ctx, ref) {
|
|
14675
|
-
const credentials = ctx.get("credentials");
|
|
14676
|
-
if (credentials === void 0) return void 0;
|
|
14677
|
-
const found = await credentials.resolve(credentialRef(ref));
|
|
14678
|
-
return found === void 0 ? void 0 : found.value;
|
|
14679
|
-
}
|
|
14680
|
-
/** Route authentication retains DSH's launch-plane fallback only when Credentials is absent. */
|
|
14681
|
-
async function routeApiKey(ctx, ref) {
|
|
14682
|
-
if (ctx.get("credentials") !== void 0) return resolveApiKey(ctx, ref);
|
|
14683
|
-
const ambient = launchEnvironmentOf(ctx).get(credentialRef(ref));
|
|
14684
|
-
return ambient !== void 0 && ambient.value.length > 0 ? ambient.value : void 0;
|
|
14685
|
-
}
|
|
14686
|
-
/** Readiness follows the same source boundary as routeApiKey, without resolving secrets. */
|
|
14687
|
-
async function routeKeyConfigured(ctx, ref, source) {
|
|
14688
|
-
const credentials = ctx.get("credentials");
|
|
14689
|
-
if (credentials !== void 0) return (await credentials.describe(credentialRef(ref))).configured;
|
|
14690
|
-
if (source === "credentials") return false;
|
|
14691
|
-
const ambient = launchEnvironmentOf(ctx).get(credentialRef(ref));
|
|
14692
|
-
return ambient !== void 0 && ambient.value.length > 0;
|
|
14693
|
-
}
|
|
14694
|
-
/** Native providers use the same credential source as DSH model routes. */
|
|
14695
|
-
function nativeAuth(ctx, ref, secrets = /* @__PURE__ */ new Set()) {
|
|
14696
|
-
const apiKeyEnv = credentialRef(ref);
|
|
14697
|
-
let resolverFailed = false;
|
|
14698
|
-
const tracked = async (resolve) => {
|
|
14699
|
-
try {
|
|
14700
|
-
const value = await resolve();
|
|
14701
|
-
if (value !== void 0) secrets.add(value);
|
|
14702
|
-
return value;
|
|
14703
|
-
} catch {
|
|
14704
|
-
resolverFailed = true;
|
|
14705
|
-
throw new Error("Native credential resolution failed");
|
|
14706
|
-
}
|
|
14707
|
-
};
|
|
14708
|
-
return {
|
|
14709
|
-
apiKeyEnv,
|
|
14710
|
-
secrets,
|
|
14711
|
-
resolverFailed: () => resolverFailed,
|
|
14712
|
-
resolveAccountToken: (endpoint) => tracked(async () => {
|
|
14713
|
-
const resolve = accountTokenFor(ctx, ctx.get("agents")?.currentInitiator());
|
|
14714
|
-
return resolve === void 0 ? void 0 : await resolve(endpoint);
|
|
14715
|
-
}),
|
|
14716
|
-
resolveApiKey: () => tracked(() => routeApiKey(ctx, ref))
|
|
14717
|
-
};
|
|
14718
|
-
}
|
|
14719
|
-
/** Account readiness uses the next selected model, while request authentication uses its frozen route. */
|
|
14720
|
-
async function credentialReadiness(ctx, agent, options) {
|
|
14721
|
-
const account = options.endpoint !== void 0 && nextModel(ctx, agent)?.provider === "deepseek-account" ? ctx.get("deepseekAccount") : void 0;
|
|
14722
|
-
if (account !== void 0 && await account.resolveToken(options.endpoint) !== void 0) return {
|
|
14723
|
-
access: "account",
|
|
14724
|
-
status: "ready"
|
|
14725
|
-
};
|
|
14726
|
-
const credentials = ctx.get("credentials");
|
|
14727
|
-
return {
|
|
14728
|
-
access: "api-key",
|
|
14729
|
-
status: (credentials === void 0 ? void 0 : await credentials.describe(credentialRef(options.credentialRef)))?.configured === true ? "ready" : "needs-credential"
|
|
14730
|
-
};
|
|
14731
|
-
}
|
|
14732
|
-
/** Account tokens are available only on a resolved deepseek-account request route. */
|
|
14733
|
-
function accountTokenFor(ctx, agent) {
|
|
14734
|
-
if (agent === void 0 || requestModel(agent)?.provider !== "deepseek-account") return void 0;
|
|
14735
|
-
const account = ctx.get("deepseekAccount");
|
|
14736
|
-
return account === void 0 ? void 0 : (endpoint) => account.resolveToken(endpoint);
|
|
14737
|
-
}
|
|
14738
|
-
//#endregion
|
|
14739
|
-
//#region ../core/src/host/pairings.ts
|
|
14740
|
-
const keyOf = ({ provider, model }) => JSON.stringify([provider, model]);
|
|
14741
|
-
const modelKey = tuple([string$1(), string$1()]);
|
|
14742
|
-
var Pairings = class {
|
|
14743
|
-
storage;
|
|
14744
|
-
constructor(storage) {
|
|
14745
|
-
this.storage = storage;
|
|
14746
|
-
}
|
|
14747
|
-
async get(model) {
|
|
14748
|
-
const record = (await this.storage.domain()).table("pairings").get(keyOf(model));
|
|
14749
|
-
return record === void 0 ? void 0 : record.connectionId;
|
|
14750
|
-
}
|
|
14751
|
-
async set(model, connectionId) {
|
|
14752
|
-
await (await this.storage.domain()).table("pairings").put(keyOf(model), { connectionId });
|
|
14753
|
-
}
|
|
14754
|
-
async list() {
|
|
14755
|
-
return [...(await this.storage.domain()).table("pairings").entries()].map(([key, value]) => {
|
|
14756
|
-
const [provider, model] = modelKey.parse(JSON.parse(key));
|
|
14757
|
-
return {
|
|
14758
|
-
provider,
|
|
14759
|
-
model,
|
|
14760
|
-
connectionId: value.connectionId
|
|
14761
|
-
};
|
|
14762
|
-
});
|
|
14763
|
-
}
|
|
14764
|
-
async forget(model) {
|
|
14765
|
-
await (await this.storage.domain()).table("pairings").delete(keyOf(model));
|
|
14766
|
-
}
|
|
14767
|
-
};
|
|
14768
|
-
//#endregion
|
|
14769
|
-
//#region ../core/src/host/plans.ts
|
|
14770
|
-
function planFor(selection) {
|
|
14771
|
-
const { connectionId, options } = selection;
|
|
14772
|
-
if (connectionId === null) return { kind: "off" };
|
|
14773
|
-
return {
|
|
14774
|
-
kind: "ready",
|
|
14775
|
-
connectionId,
|
|
14776
|
-
options: structuredClone(options)
|
|
14777
|
-
};
|
|
14778
|
-
}
|
|
14779
|
-
/** Concurrent capture is shared; only failed captures may retry in the same step. */
|
|
14780
|
-
var Snapshots = class {
|
|
14781
|
-
steps = /* @__PURE__ */ new WeakMap();
|
|
14782
|
-
capture(agent, turn, step, plan) {
|
|
14783
|
-
const previous = this.steps.get(agent);
|
|
14784
|
-
if (previous !== void 0 && previous.turn === turn && previous.step === step) {
|
|
14785
|
-
if (previous.kind === "capturing") return previous.pending;
|
|
14786
|
-
if (previous.plan.kind !== "failed") return Promise.resolve();
|
|
14787
|
-
}
|
|
14788
|
-
const pending = plan().then((result) => {
|
|
14789
|
-
if (this.steps.get(agent) === snapshot) this.steps.set(agent, {
|
|
14790
|
-
turn,
|
|
14791
|
-
step,
|
|
14792
|
-
kind: "captured",
|
|
14793
|
-
plan: result
|
|
14794
|
-
});
|
|
14795
|
-
}, (error) => {
|
|
14796
|
-
if (this.steps.get(agent) === snapshot) this.steps.delete(agent);
|
|
14797
|
-
throw error;
|
|
14798
|
-
});
|
|
14799
|
-
const snapshot = {
|
|
14800
|
-
turn,
|
|
14801
|
-
step,
|
|
14802
|
-
kind: "capturing",
|
|
14803
|
-
pending
|
|
14804
|
-
};
|
|
14805
|
-
this.steps.set(agent, snapshot);
|
|
14806
|
-
return pending;
|
|
14807
|
-
}
|
|
14808
|
-
get(agent) {
|
|
14809
|
-
const snapshot = this.steps.get(agent);
|
|
14810
|
-
return snapshot?.kind === "captured" ? snapshot.plan : void 0;
|
|
14811
|
-
}
|
|
14812
|
-
forget(agent) {
|
|
14813
|
-
this.steps.delete(agent);
|
|
14814
|
-
}
|
|
14815
|
-
};
|
|
14816
14674
|
//#endregion
|
|
14817
14675
|
//#region ../core/src/shared/ids.ts
|
|
14818
14676
|
const SEARCH = {
|
|
14819
14677
|
packageName: "dsh-web-search-enhanced",
|
|
14820
14678
|
rowId: "web-search-enhanced",
|
|
14821
|
-
providerId: "search-enhanced"
|
|
14679
|
+
providerId: "search-enhanced",
|
|
14680
|
+
tools: {
|
|
14681
|
+
action: "web_search_enhanced",
|
|
14682
|
+
list: "list_web_search_providers"
|
|
14683
|
+
},
|
|
14684
|
+
command: "setup-search"
|
|
14822
14685
|
};
|
|
14823
14686
|
//#endregion
|
|
14824
14687
|
//#region ../core/src/shared/route.ts
|
|
@@ -15002,48 +14865,560 @@ function selectionContract(spec) {
|
|
|
15002
14865
|
};
|
|
15003
14866
|
}
|
|
15004
14867
|
//#endregion
|
|
15005
|
-
//#region ../core/src/host/
|
|
15006
|
-
/**
|
|
15007
|
-
|
|
15008
|
-
|
|
14868
|
+
//#region ../core/src/host/enhanced-parts/wording.ts
|
|
14869
|
+
/** Why an id the operator added after the conversation started is unknown here (the list is fixed per conversation). */
|
|
14870
|
+
const LATER_PROVIDERS = ". Providers added or turned on after this conversation started are available only in new conversations.";
|
|
14871
|
+
/** At most this many ready ids are named in one message; the list tool shows the rest. */
|
|
14872
|
+
const READY_SHOWN = 20;
|
|
14873
|
+
/**
|
|
14874
|
+
* The tail of a message naming the ready providers the Agent can switch to:
|
|
14875
|
+
* `ready providers: <ids>` without `except`, at most twenty in list order, or
|
|
14876
|
+
* `no other provider is ready` in place of the whole clause. When readiness could
|
|
14877
|
+
* not be read, the list tool is named instead, so "none" is never claimed unread.
|
|
14878
|
+
*/
|
|
14879
|
+
function readyClause(voice, ready, except) {
|
|
14880
|
+
if (ready.kind === "unread") return `${voice.identity.tools.list} shows the ready providers`;
|
|
14881
|
+
const others = ready.ids.filter((id) => id !== except);
|
|
14882
|
+
if (others.length === 0) return "no other provider is ready";
|
|
14883
|
+
const hidden = others.length - READY_SHOWN;
|
|
14884
|
+
const more = hidden > 0 ? ` and ${hidden} more (${voice.identity.tools.list})` : "";
|
|
14885
|
+
return `ready providers: ${others.slice(0, READY_SHOWN).join(", ")}${more}`;
|
|
14886
|
+
}
|
|
14887
|
+
/** Word a refusal core reported before any backend ran; `unavailable` passes core's own error on unchanged. */
|
|
14888
|
+
function rejectionError(voice, reason) {
|
|
14889
|
+
const { error } = capabilityErrors(voice.identity.packageName);
|
|
14890
|
+
switch (reason.kind) {
|
|
14891
|
+
case "unknown": return error("WEB_PROVIDER_CONFIGURED_MISSING", `unknown provider "${reason.connectionId}"; ${readyClause(voice, reason.ready, reason.connectionId)}${LATER_PROVIDERS}`);
|
|
14892
|
+
case "removed": return error("WEB_PROVIDER_CONFIGURED_MISSING", `provider "${reason.connectionId}" was removed by the user; ${readyClause(voice, reason.ready, reason.connectionId)}`);
|
|
14893
|
+
case "off": return error("WEB_PROVIDER_UNAVAILABLE", `${voice.capability} is off for this session; only the user can turn it back on (composer menu). Do not retry.`);
|
|
14894
|
+
case "withdrawn": return error("WEB_PROVIDER_UNAVAILABLE", `${voice.identity.tools.action} is unavailable now; use ${voice.nativeTool}.`);
|
|
14895
|
+
case "unavailable": return reason.error;
|
|
14896
|
+
}
|
|
14897
|
+
}
|
|
14898
|
+
/**
|
|
14899
|
+
* Point a backend failure at the list tool: the hint ends the first paragraph,
|
|
14900
|
+
* before any untrusted upstream explanation, after a full stop when that paragraph
|
|
14901
|
+
* has no sentence end; the code and the rest of the message stay as the execution
|
|
14902
|
+
* layer wrote them. Cancellation, and failures `unhinted` marks, pass on unchanged.
|
|
14903
|
+
*/
|
|
14904
|
+
function providerFailure(voice, cause, connectionId, unhinted) {
|
|
14905
|
+
if (!(cause instanceof WebError) || cause.code === "WEB_ABORTED" || unhinted(cause)) return cause;
|
|
14906
|
+
const hint = ` Provider "${connectionId}" failed; ${voice.identity.tools.list} shows the others.`;
|
|
14907
|
+
const end = cause.message.indexOf("\n\n");
|
|
14908
|
+
const first = end === -1 ? cause.message : cause.message.slice(0, end);
|
|
14909
|
+
const rest = end === -1 ? "" : cause.message.slice(end);
|
|
14910
|
+
const stop = /[.!?]$/u.test(first) ? "" : ".";
|
|
14911
|
+
return new WebError(first + stop + hint + rest, cause.code);
|
|
14912
|
+
}
|
|
14913
|
+
/** An argument error in DSH's own form, without the package prefix. */
|
|
14914
|
+
function invalidArguments(problem) {
|
|
14915
|
+
return /* @__PURE__ */ new Error(`invalid arguments: ${problem}`);
|
|
14916
|
+
}
|
|
14917
|
+
/** DSH accepts extra properties on the open argument root; the enhanced tools refuse them without echoing the name. */
|
|
14918
|
+
function rejectUnknownProperties(args, known) {
|
|
14919
|
+
if (Object.keys(args).some((key) => !known.includes(key))) throw invalidArguments("unknown property");
|
|
14920
|
+
}
|
|
14921
|
+
/** The caller DSH reports for this execution; a call without an Agent never falls back to a default connection. */
|
|
14922
|
+
function explicitCall(tool, exec) {
|
|
14923
|
+
if (exec.agent === void 0) throw new Error(`${tool} can only be called by an Agent`);
|
|
14924
|
+
return {
|
|
14925
|
+
agent: exec.agent,
|
|
14926
|
+
signal: exec.signal
|
|
14927
|
+
};
|
|
14928
|
+
}
|
|
14929
|
+
/**
|
|
14930
|
+
* The model's `provider` argument, once it has the grammar of a connection id.
|
|
14931
|
+
* A malformed value is never echoed; the error names the ready providers instead.
|
|
14932
|
+
*/
|
|
14933
|
+
async function providerArgument(voice, value, access, call) {
|
|
14934
|
+
if (value.length > 0 && value.length <= 128 && isConnectionId(value)) return value;
|
|
14935
|
+
throw invalidArguments(`provider must be an id such as builtin:<key> or custom:<name>; ${readyClause(voice, await access.ready(call))}`);
|
|
14936
|
+
}
|
|
14937
|
+
/**
|
|
14938
|
+
* Run one request on the named provider: a refusal is worded for the model,
|
|
14939
|
+
* a backend failure gets the switch-provider hint (docs/design.md, "增强工具的错误").
|
|
14940
|
+
*/
|
|
14941
|
+
async function runOnProvider(voice, access, connectionId, request, call, unhinted = () => false) {
|
|
14942
|
+
let outcome;
|
|
14943
|
+
try {
|
|
14944
|
+
outcome = await access.run(connectionId, request, call);
|
|
14945
|
+
} catch (cause) {
|
|
14946
|
+
throw providerFailure(voice, cause, connectionId, unhinted);
|
|
14947
|
+
}
|
|
14948
|
+
if (outcome.kind === "rejected") throw rejectionError(voice, outcome.reason);
|
|
14949
|
+
return outcome.value;
|
|
15009
14950
|
}
|
|
15010
14951
|
//#endregion
|
|
15011
|
-
//#region ../core/src/host/
|
|
15012
|
-
|
|
15013
|
-
|
|
15014
|
-
|
|
14952
|
+
//#region ../core/src/host/enhanced-parts/listing.ts
|
|
14953
|
+
/** The recipient `sendsTo` names for fetch `local`; the text says `fetches from this machine`. */
|
|
14954
|
+
const THIS_MACHINE = "this machine";
|
|
14955
|
+
const SESSION_MODEL = "the service behind the session model";
|
|
14956
|
+
const HOST_MAX = 253;
|
|
14957
|
+
/** Why the session model cannot search, in English; the execution layer's error and the listing share it. */
|
|
14958
|
+
const UNSUPPORTED_REASON_TEXT = {
|
|
14959
|
+
"no-model": "no session model is available",
|
|
14960
|
+
"unknown-route": "the model route is unknown",
|
|
14961
|
+
protocol: "the route does not declare a supported protocol",
|
|
14962
|
+
endpoint: "the route has no supported endpoint",
|
|
14963
|
+
headers: "the route uses custom request headers",
|
|
14964
|
+
credential: "the route has no supported credential reference"
|
|
14965
|
+
};
|
|
14966
|
+
const text$2 = {
|
|
14967
|
+
type: "string",
|
|
14968
|
+
required: true
|
|
14969
|
+
};
|
|
14970
|
+
function entrySchema(extra) {
|
|
14971
|
+
const common = {
|
|
14972
|
+
id: text$2,
|
|
14973
|
+
name: text$2,
|
|
14974
|
+
use: { type: "string" },
|
|
14975
|
+
sendsTo: text$2,
|
|
14976
|
+
...extra
|
|
14977
|
+
};
|
|
14978
|
+
return { oneOf: [
|
|
14979
|
+
{
|
|
14980
|
+
type: "object",
|
|
14981
|
+
additionalProperties: false,
|
|
14982
|
+
properties: {
|
|
14983
|
+
...common,
|
|
14984
|
+
status: {
|
|
14985
|
+
type: "string",
|
|
14986
|
+
required: true,
|
|
14987
|
+
const: "ready"
|
|
14988
|
+
}
|
|
14989
|
+
}
|
|
14990
|
+
},
|
|
14991
|
+
{
|
|
14992
|
+
type: "object",
|
|
14993
|
+
additionalProperties: false,
|
|
14994
|
+
properties: {
|
|
14995
|
+
...common,
|
|
14996
|
+
status: {
|
|
14997
|
+
type: "string",
|
|
14998
|
+
required: true,
|
|
14999
|
+
const: "needs-key"
|
|
15000
|
+
}
|
|
15001
|
+
}
|
|
15002
|
+
},
|
|
15003
|
+
{
|
|
15004
|
+
type: "object",
|
|
15005
|
+
additionalProperties: false,
|
|
15006
|
+
properties: {
|
|
15007
|
+
...common,
|
|
15008
|
+
status: {
|
|
15009
|
+
type: "string",
|
|
15010
|
+
required: true,
|
|
15011
|
+
const: "needs-sign-in"
|
|
15012
|
+
}
|
|
15013
|
+
}
|
|
15014
|
+
},
|
|
15015
|
+
{
|
|
15016
|
+
type: "object",
|
|
15017
|
+
additionalProperties: false,
|
|
15018
|
+
properties: {
|
|
15019
|
+
...common,
|
|
15020
|
+
status: {
|
|
15021
|
+
type: "string",
|
|
15022
|
+
required: true,
|
|
15023
|
+
const: "unavailable"
|
|
15024
|
+
},
|
|
15025
|
+
reason: text$2
|
|
15026
|
+
}
|
|
15027
|
+
}
|
|
15028
|
+
] };
|
|
15029
|
+
}
|
|
15030
|
+
const session = {
|
|
15031
|
+
sessionProvider: text$2,
|
|
15032
|
+
sessionProviderTurnedOff: {
|
|
15033
|
+
type: "boolean",
|
|
15034
|
+
required: true
|
|
15015
15035
|
}
|
|
15016
15036
|
};
|
|
15017
|
-
|
|
15018
|
-
|
|
15019
|
-
|
|
15020
|
-
|
|
15021
|
-
|
|
15022
|
-
|
|
15023
|
-
|
|
15024
|
-
|
|
15025
|
-
|
|
15026
|
-
|
|
15027
|
-
|
|
15028
|
-
|
|
15029
|
-
|
|
15030
|
-
|
|
15031
|
-
|
|
15037
|
+
function searchListingSchema() {
|
|
15038
|
+
return {
|
|
15039
|
+
type: "object",
|
|
15040
|
+
additionalProperties: false,
|
|
15041
|
+
properties: {
|
|
15042
|
+
...session,
|
|
15043
|
+
freshness: {
|
|
15044
|
+
type: "string",
|
|
15045
|
+
required: true,
|
|
15046
|
+
enum: FRESHNESS
|
|
15047
|
+
},
|
|
15048
|
+
providers: {
|
|
15049
|
+
type: "array",
|
|
15050
|
+
required: true,
|
|
15051
|
+
items: entrySchema({ appliesFreshness: {
|
|
15052
|
+
type: "boolean",
|
|
15053
|
+
required: true
|
|
15054
|
+
} })
|
|
15055
|
+
}
|
|
15056
|
+
}
|
|
15057
|
+
};
|
|
15058
|
+
}
|
|
15059
|
+
/** This conversation's providers, or the worded refusal. */
|
|
15060
|
+
async function listProviders(voice, access, call) {
|
|
15061
|
+
const outcome = await access.list(call);
|
|
15062
|
+
if (outcome.kind === "rejected") throw rejectionError(voice, outcome.reason);
|
|
15063
|
+
return outcome.value;
|
|
15064
|
+
}
|
|
15065
|
+
function searchListingValue(listing) {
|
|
15066
|
+
return {
|
|
15067
|
+
...sessionOf(listing),
|
|
15068
|
+
freshness: listing.options.freshness,
|
|
15069
|
+
providers: listing.providers.map((entry) => ({
|
|
15070
|
+
...listedBase(entry),
|
|
15071
|
+
appliesFreshness: entry.facts.appliesFreshness === true,
|
|
15072
|
+
...readiness(entry)
|
|
15073
|
+
}))
|
|
15074
|
+
};
|
|
15075
|
+
}
|
|
15076
|
+
/** One line per provider, `<id> — <name> · …`, without a heading (docs/design.md, "列出工具"). */
|
|
15077
|
+
function renderSearchListing(voice, value) {
|
|
15078
|
+
const uncached = value.freshness !== "auto";
|
|
15079
|
+
return renderLines(voice, value, uncached ? ["The user asked for uncached (fresh) page content; only providers marked \"uncached\" apply it."] : [], {
|
|
15080
|
+
marks: (entry) => uncached && entry.appliesFreshness ? ["uncached"] : [],
|
|
15081
|
+
notes: () => []
|
|
15082
|
+
});
|
|
15083
|
+
}
|
|
15084
|
+
function renderLines(voice, value, head, parts) {
|
|
15085
|
+
const entries = value.providers.map((entry) => {
|
|
15086
|
+
const line = [statusText$1(entry)];
|
|
15087
|
+
if (entry.id === value.sessionProvider) line.push("selected for this session");
|
|
15088
|
+
if (entry.use !== void 0) line.push(entry.use);
|
|
15089
|
+
line.push(...parts.marks(entry));
|
|
15090
|
+
if (entry.sendsTo === "this machine") line.push("fetches from this machine");
|
|
15091
|
+
else if (entry.sendsTo !== entry.name) line.push(`${voice.sends} go to ${entry.sendsTo}`);
|
|
15092
|
+
line.push(...parts.notes(entry));
|
|
15093
|
+
return `${entry.id} — ${entry.name} · ${line.join(" · ")}`;
|
|
15094
|
+
});
|
|
15095
|
+
const tail = value.sessionProviderTurnedOff ? [`This session's selection, ${value.sessionProvider}, is turned off; ${voice.nativeTool} will fail until the user changes it.`] : [];
|
|
15096
|
+
return [
|
|
15097
|
+
...head,
|
|
15098
|
+
...entries.length === 0 ? ["(no providers)"] : entries,
|
|
15099
|
+
...tail
|
|
15100
|
+
].join("\n");
|
|
15101
|
+
}
|
|
15102
|
+
function statusText$1(entry) {
|
|
15103
|
+
switch (entry.status) {
|
|
15104
|
+
case "ready": return "ready";
|
|
15105
|
+
case "needs-key": return "needs an API key (ask the user)";
|
|
15106
|
+
case "needs-sign-in": return "needs the user to sign in to DeepSeek again";
|
|
15107
|
+
case "unavailable": return `unavailable: ${entry.reason}`;
|
|
15032
15108
|
}
|
|
15033
|
-
|
|
15034
|
-
|
|
15109
|
+
}
|
|
15110
|
+
function sessionOf(listing) {
|
|
15111
|
+
return {
|
|
15112
|
+
sessionProvider: listing.session.connectionId,
|
|
15113
|
+
sessionProviderTurnedOff: !listing.session.selectable
|
|
15114
|
+
};
|
|
15115
|
+
}
|
|
15116
|
+
function listedBase(entry) {
|
|
15117
|
+
const { use, recipient } = entry.facts;
|
|
15118
|
+
return {
|
|
15119
|
+
id: entry.id,
|
|
15120
|
+
name: oneLine(entry.name, 200),
|
|
15121
|
+
...use === void 0 ? {} : { use },
|
|
15122
|
+
sendsTo: recipientText(recipient)
|
|
15123
|
+
};
|
|
15124
|
+
}
|
|
15125
|
+
function recipientText(recipient) {
|
|
15126
|
+
switch (recipient.kind) {
|
|
15127
|
+
case "this-machine": return THIS_MACHINE;
|
|
15128
|
+
case "session-model": return recipient.provider === void 0 ? SESSION_MODEL : `${SESSION_MODEL} (${oneLine(recipient.provider, 200)})`;
|
|
15129
|
+
case "service": return recipient.host === void 0 ? oneLine(recipient.service, 200) : oneLine(recipient.host, HOST_MAX);
|
|
15130
|
+
}
|
|
15131
|
+
}
|
|
15132
|
+
function readiness(entry) {
|
|
15133
|
+
switch (entry.status) {
|
|
15134
|
+
case "ready": return { status: "ready" };
|
|
15135
|
+
case "needs-credential": return { status: entry.credential === "account" ? "needs-sign-in" : "needs-key" };
|
|
15136
|
+
case "unsupported": return {
|
|
15137
|
+
status: "unavailable",
|
|
15138
|
+
reason: UNSUPPORTED_REASON_TEXT[entry.reason]
|
|
15139
|
+
};
|
|
15140
|
+
}
|
|
15141
|
+
}
|
|
15142
|
+
/** Collapse line breaks, controls and runs of white space, then cut at `max` characters without splitting a surrogate pair. */
|
|
15143
|
+
function oneLine(value, max) {
|
|
15144
|
+
const flat = value.replace(/[\p{Cc}\p{Zl}\p{Zp}\s]+/gu, " ").trim();
|
|
15145
|
+
if (flat.length <= max) return flat;
|
|
15146
|
+
const last = flat.charCodeAt(max - 2);
|
|
15147
|
+
return flat.slice(0, last >= 55296 && last <= 56319 ? max - 2 : max - 1) + "…";
|
|
15148
|
+
}
|
|
15149
|
+
function nextModel(ctx, agent) {
|
|
15150
|
+
const pending = ctx.get("sessionProjections")?.stateOf(agent.session, "modelSelection")?.pending;
|
|
15151
|
+
if (pending !== void 0 && pending !== null) return complete(pending);
|
|
15152
|
+
const request = agent.session.requestHeader();
|
|
15153
|
+
return complete(request === void 0 ? agent.options : request.config);
|
|
15154
|
+
}
|
|
15155
|
+
function complete(value) {
|
|
15156
|
+
if (!value.provider || !value.model) return void 0;
|
|
15157
|
+
return {
|
|
15158
|
+
provider: value.provider,
|
|
15159
|
+
model: value.model
|
|
15160
|
+
};
|
|
15161
|
+
}
|
|
15162
|
+
/** The resolved route used for native account authentication; pending choices do not authenticate. */
|
|
15163
|
+
function requestModel(agent) {
|
|
15164
|
+
const request = agent.session.requestContext();
|
|
15165
|
+
return request === void 0 ? void 0 : complete(request);
|
|
15166
|
+
}
|
|
15167
|
+
//#endregion
|
|
15168
|
+
//#region ../core/src/host/credentials.ts
|
|
15169
|
+
/** Credentials is authoritative; absence does not fall back to process.env. */
|
|
15170
|
+
async function resolveApiKey(ctx, ref) {
|
|
15171
|
+
const credentials = ctx.get("credentials");
|
|
15172
|
+
if (credentials === void 0) return void 0;
|
|
15173
|
+
const found = await credentials.resolve(credentialRef(ref));
|
|
15174
|
+
return found === void 0 ? void 0 : found.value;
|
|
15175
|
+
}
|
|
15176
|
+
/** Route authentication retains DSH's launch-plane fallback only when Credentials is absent. */
|
|
15177
|
+
async function routeApiKey(ctx, ref) {
|
|
15178
|
+
if (ctx.get("credentials") !== void 0) return resolveApiKey(ctx, ref);
|
|
15179
|
+
const ambient = launchEnvironmentOf(ctx).get(credentialRef(ref));
|
|
15180
|
+
return ambient !== void 0 && ambient.value.length > 0 ? ambient.value : void 0;
|
|
15181
|
+
}
|
|
15182
|
+
/** Readiness follows the same source boundary as routeApiKey, without resolving secrets. */
|
|
15183
|
+
async function routeKeyConfigured(ctx, ref, source) {
|
|
15184
|
+
const credentials = ctx.get("credentials");
|
|
15185
|
+
if (credentials !== void 0) return (await credentials.describe(credentialRef(ref))).configured;
|
|
15186
|
+
if (source === "credentials") return false;
|
|
15187
|
+
const ambient = launchEnvironmentOf(ctx).get(credentialRef(ref));
|
|
15188
|
+
return ambient !== void 0 && ambient.value.length > 0;
|
|
15189
|
+
}
|
|
15190
|
+
/** Native providers use the same credential source as DSH model routes. */
|
|
15191
|
+
function nativeAuth(ctx, ref, secrets = /* @__PURE__ */ new Set()) {
|
|
15192
|
+
const apiKeyEnv = credentialRef(ref);
|
|
15193
|
+
let resolverFailed = false;
|
|
15194
|
+
const tracked = async (resolve) => {
|
|
15195
|
+
try {
|
|
15196
|
+
const value = await resolve();
|
|
15197
|
+
if (value !== void 0) secrets.add(value);
|
|
15198
|
+
return value;
|
|
15199
|
+
} catch {
|
|
15200
|
+
resolverFailed = true;
|
|
15201
|
+
throw new Error("Native credential resolution failed");
|
|
15202
|
+
}
|
|
15203
|
+
};
|
|
15204
|
+
return {
|
|
15205
|
+
apiKeyEnv,
|
|
15206
|
+
secrets,
|
|
15207
|
+
resolverFailed: () => resolverFailed,
|
|
15208
|
+
resolveAccountToken: (endpoint) => tracked(async () => {
|
|
15209
|
+
const resolve = accountTokenFor(ctx, ctx.get("agents")?.currentInitiator());
|
|
15210
|
+
return resolve === void 0 ? void 0 : await resolve(endpoint);
|
|
15211
|
+
}),
|
|
15212
|
+
resolveApiKey: () => tracked(() => routeApiKey(ctx, ref))
|
|
15213
|
+
};
|
|
15214
|
+
}
|
|
15215
|
+
/** Account readiness uses the next selected model, while request authentication uses its frozen route. */
|
|
15216
|
+
async function credentialReadiness(ctx, model, options) {
|
|
15217
|
+
const account = options.endpoint !== void 0 && model?.provider === "deepseek-account" ? ctx.get("deepseekAccount") : void 0;
|
|
15218
|
+
if (account !== void 0 && await account.resolveToken(options.endpoint) !== void 0) return {
|
|
15219
|
+
access: "account",
|
|
15220
|
+
status: "ready"
|
|
15221
|
+
};
|
|
15222
|
+
const credentials = ctx.get("credentials");
|
|
15223
|
+
return {
|
|
15224
|
+
access: "api-key",
|
|
15225
|
+
status: (credentials === void 0 ? void 0 : await credentials.describe(credentialRef(options.credentialRef)))?.configured === true ? "ready" : "needs-credential"
|
|
15226
|
+
};
|
|
15227
|
+
}
|
|
15228
|
+
/** Account tokens are available only on a resolved deepseek-account request route. */
|
|
15229
|
+
function accountTokenFor(ctx, agent) {
|
|
15230
|
+
if (agent === void 0 || requestModel(agent)?.provider !== "deepseek-account") return void 0;
|
|
15231
|
+
const account = ctx.get("deepseekAccount");
|
|
15232
|
+
return account === void 0 ? void 0 : (endpoint) => account.resolveToken(endpoint);
|
|
15233
|
+
}
|
|
15234
|
+
//#endregion
|
|
15235
|
+
//#region ../core/src/host/pairings.ts
|
|
15236
|
+
const keyOf = ({ provider, model }) => JSON.stringify([provider, model]);
|
|
15237
|
+
const modelKey = tuple([string$1(), string$1()]);
|
|
15238
|
+
var Pairings = class {
|
|
15239
|
+
storage;
|
|
15240
|
+
constructor(storage) {
|
|
15241
|
+
this.storage = storage;
|
|
15242
|
+
}
|
|
15243
|
+
async get(model) {
|
|
15244
|
+
const record = (await this.storage.domain()).table("pairings").get(keyOf(model));
|
|
15245
|
+
return record === void 0 ? void 0 : record.connectionId;
|
|
15246
|
+
}
|
|
15247
|
+
async set(model, connectionId) {
|
|
15248
|
+
await (await this.storage.domain()).table("pairings").put(keyOf(model), { connectionId });
|
|
15249
|
+
}
|
|
15250
|
+
async list() {
|
|
15251
|
+
return [...(await this.storage.domain()).table("pairings").entries()].map(([key, value]) => {
|
|
15252
|
+
const [provider, model] = modelKey.parse(JSON.parse(key));
|
|
15253
|
+
return {
|
|
15254
|
+
provider,
|
|
15255
|
+
model,
|
|
15256
|
+
connectionId: value.connectionId
|
|
15257
|
+
};
|
|
15258
|
+
});
|
|
15259
|
+
}
|
|
15260
|
+
async forget(model) {
|
|
15261
|
+
await (await this.storage.domain()).table("pairings").delete(keyOf(model));
|
|
15262
|
+
}
|
|
15263
|
+
};
|
|
15264
|
+
//#endregion
|
|
15265
|
+
//#region ../core/src/host/plans.ts
|
|
15266
|
+
function planFor(selection) {
|
|
15267
|
+
const { connectionId, options } = selection;
|
|
15268
|
+
if (connectionId === null) return { kind: "off" };
|
|
15269
|
+
return {
|
|
15270
|
+
kind: "ready",
|
|
15271
|
+
connectionId,
|
|
15272
|
+
options: structuredClone(options)
|
|
15273
|
+
};
|
|
15274
|
+
}
|
|
15275
|
+
/** Concurrent capture is shared; only failed captures may retry in the same step. */
|
|
15276
|
+
var Snapshots = class {
|
|
15277
|
+
steps = /* @__PURE__ */ new WeakMap();
|
|
15278
|
+
capture(agent, turn, step, plan) {
|
|
15279
|
+
const previous = this.steps.get(agent);
|
|
15280
|
+
if (previous !== void 0 && previous.turn === turn && previous.step === step) {
|
|
15281
|
+
if (previous.kind === "capturing") return previous.pending;
|
|
15282
|
+
if (previous.plan.kind !== "failed") return Promise.resolve();
|
|
15283
|
+
}
|
|
15284
|
+
const pending = plan().then((result) => {
|
|
15285
|
+
if (this.steps.get(agent) === snapshot) this.steps.set(agent, {
|
|
15286
|
+
turn,
|
|
15287
|
+
step,
|
|
15288
|
+
kind: "captured",
|
|
15289
|
+
plan: result
|
|
15290
|
+
});
|
|
15291
|
+
}, (error) => {
|
|
15292
|
+
if (this.steps.get(agent) === snapshot) this.steps.delete(agent);
|
|
15293
|
+
throw error;
|
|
15294
|
+
});
|
|
15295
|
+
const snapshot = {
|
|
15296
|
+
turn,
|
|
15297
|
+
step,
|
|
15298
|
+
kind: "capturing",
|
|
15299
|
+
pending
|
|
15300
|
+
};
|
|
15301
|
+
this.steps.set(agent, snapshot);
|
|
15302
|
+
return pending;
|
|
15303
|
+
}
|
|
15304
|
+
get(agent) {
|
|
15305
|
+
const snapshot = this.steps.get(agent);
|
|
15306
|
+
return snapshot?.kind === "captured" ? snapshot.plan : void 0;
|
|
15307
|
+
}
|
|
15308
|
+
forget(agent) {
|
|
15309
|
+
this.steps.delete(agent);
|
|
15310
|
+
}
|
|
15311
|
+
};
|
|
15312
|
+
//#endregion
|
|
15313
|
+
//#region ../core/src/host/session-origin.ts
|
|
15314
|
+
function sessionOrigin(header) {
|
|
15315
|
+
const { parentSession: parent, isSeeded: seeded, origin } = header;
|
|
15316
|
+
if (origin === "subagent") return {
|
|
15317
|
+
kind: "subagent",
|
|
15318
|
+
parent,
|
|
15319
|
+
seeded
|
|
15320
|
+
};
|
|
15321
|
+
return parent === void 0 ? {
|
|
15322
|
+
kind: "top-level",
|
|
15323
|
+
seeded
|
|
15324
|
+
} : {
|
|
15325
|
+
kind: "fork",
|
|
15326
|
+
parent,
|
|
15327
|
+
seeded
|
|
15328
|
+
};
|
|
15329
|
+
}
|
|
15330
|
+
function parentOf(origin) {
|
|
15331
|
+
return origin.kind === "top-level" ? void 0 : origin.parent;
|
|
15332
|
+
}
|
|
15333
|
+
//#endregion
|
|
15334
|
+
//#region ../core/src/host/provider-lists.ts
|
|
15335
|
+
/** Live setup/created events do not make a new conversation a restored one. */
|
|
15336
|
+
function isNewConversation(session) {
|
|
15337
|
+
const origin = sessionOrigin(session.header);
|
|
15338
|
+
return parentOf(origin) === void 0 && !origin.seeded && session.firstLiveSeq === 0 && session.firstLifecycleSeq === 0;
|
|
15339
|
+
}
|
|
15340
|
+
/** Immutable conversation authorization, sharing the selection queue and storage lifetime. */
|
|
15341
|
+
var ProviderLists = class {
|
|
15342
|
+
storage;
|
|
15343
|
+
constructor(storage) {
|
|
15344
|
+
this.storage = storage;
|
|
15345
|
+
}
|
|
15346
|
+
read(agent) {
|
|
15347
|
+
return this.storage.exclusive(agent.session.id, async () => {
|
|
15348
|
+
const table = (await this.storage.domain()).table("provider_lists");
|
|
15349
|
+
return this.nonempty(table.get(agent.session.id));
|
|
15350
|
+
});
|
|
15351
|
+
}
|
|
15352
|
+
create(agent, initial) {
|
|
15353
|
+
return this.storage.exclusive(agent.session.id, async () => {
|
|
15354
|
+
const table = (await this.storage.domain()).table("provider_lists");
|
|
15355
|
+
const stored = table.get(agent.session.id);
|
|
15356
|
+
if (stored !== void 0) return this.nonempty(stored);
|
|
15357
|
+
const parent = parentOf(sessionOrigin(agent.session.header));
|
|
15358
|
+
let ids;
|
|
15359
|
+
if (parent !== void 0) {
|
|
15360
|
+
await this.storage.settled(parent);
|
|
15361
|
+
ids = table.get(parent);
|
|
15362
|
+
} else ids = initial;
|
|
15363
|
+
if (ids === void 0) return void 0;
|
|
15364
|
+
await table.put(agent.session.id, [...ids]);
|
|
15365
|
+
return this.nonempty(ids);
|
|
15366
|
+
});
|
|
15367
|
+
}
|
|
15368
|
+
nonempty(ids) {
|
|
15369
|
+
return ids === void 0 || ids.length === 0 ? void 0 : [...ids];
|
|
15370
|
+
}
|
|
15371
|
+
};
|
|
15372
|
+
//#endregion
|
|
15373
|
+
//#region ../core/src/host/selectable.ts
|
|
15374
|
+
/** One eligibility rule for memories, Remote changes, plans and per-call policy. */
|
|
15375
|
+
function isSelectable(connection) {
|
|
15376
|
+
return connection !== void 0 && !connection.disabled;
|
|
15377
|
+
}
|
|
15378
|
+
//#endregion
|
|
15379
|
+
//#region ../core/src/host/sessions.ts
|
|
15380
|
+
function liveSession(agent) {
|
|
15381
|
+
return {
|
|
15382
|
+
kind: "live",
|
|
15383
|
+
id: agent.session.id,
|
|
15384
|
+
header: agent.session.header,
|
|
15385
|
+
agent
|
|
15386
|
+
};
|
|
15387
|
+
}
|
|
15388
|
+
var SelectionConflict = class extends Error {
|
|
15389
|
+
constructor() {
|
|
15390
|
+
super("the selection changed since it was read");
|
|
15391
|
+
}
|
|
15392
|
+
};
|
|
15393
|
+
var Sessions = class {
|
|
15394
|
+
ctx;
|
|
15395
|
+
settings;
|
|
15396
|
+
storage;
|
|
15397
|
+
pairings;
|
|
15398
|
+
restriction;
|
|
15399
|
+
describe;
|
|
15400
|
+
constructor(ctx, settings, storage, pairings, restriction, describe = async () => []) {
|
|
15401
|
+
this.ctx = ctx;
|
|
15402
|
+
this.settings = settings;
|
|
15403
|
+
this.storage = storage;
|
|
15404
|
+
this.pairings = pairings;
|
|
15405
|
+
this.restriction = restriction;
|
|
15406
|
+
this.describe = describe;
|
|
15407
|
+
}
|
|
15408
|
+
current(target) {
|
|
15409
|
+
return this.exclusive(target.id, async () => this.read(target, await this.table()));
|
|
15035
15410
|
}
|
|
15036
15411
|
/** Resolve visibility without creating records or materializing inherited/default options. */
|
|
15037
|
-
peek(
|
|
15038
|
-
return this.exclusive(
|
|
15412
|
+
peek(target) {
|
|
15413
|
+
return this.exclusive(target.id, async () => this.read(target, await this.table(), false));
|
|
15039
15414
|
}
|
|
15040
|
-
change(
|
|
15041
|
-
return this.exclusive(
|
|
15415
|
+
change(target, change, expectedRevision) {
|
|
15416
|
+
return this.exclusive(target.id, async () => {
|
|
15042
15417
|
const table = await this.table();
|
|
15043
|
-
await this.read(
|
|
15418
|
+
await this.read(target, table);
|
|
15044
15419
|
const { connectionId, ...options } = change;
|
|
15045
15420
|
const record = this.storage.optionsCodec.toRecord(options);
|
|
15046
|
-
const updated = await table.update(
|
|
15421
|
+
const updated = await table.update(target.id, (current) => {
|
|
15047
15422
|
if (current.revision !== expectedRevision) throw new SelectionConflict();
|
|
15048
15423
|
return {
|
|
15049
15424
|
...current,
|
|
@@ -15052,9 +15427,10 @@ var Sessions = class {
|
|
|
15052
15427
|
revision: current.revision + 1
|
|
15053
15428
|
};
|
|
15054
15429
|
});
|
|
15055
|
-
this.
|
|
15056
|
-
if (
|
|
15057
|
-
|
|
15430
|
+
const agent = this.ctx.get("agents")?.get(target.id);
|
|
15431
|
+
if (agent !== void 0) this.restriction.sync(agent, updated.connectionId);
|
|
15432
|
+
if (connectionId !== void 0 && sessionOrigin(target.header).kind !== "subagent") {
|
|
15433
|
+
const model = this.model(target);
|
|
15058
15434
|
if (model !== void 0) {
|
|
15059
15435
|
const memory = await this.pairings.set(model, updated.connectionId).then(() => ({ kind: "stored" }), (error) => ({
|
|
15060
15436
|
kind: "failed",
|
|
@@ -15069,37 +15445,34 @@ var Sessions = class {
|
|
|
15069
15445
|
};
|
|
15070
15446
|
});
|
|
15071
15447
|
}
|
|
15072
|
-
connections(
|
|
15073
|
-
return this.describe(this.settings(),
|
|
15448
|
+
connections(target) {
|
|
15449
|
+
return this.describe(this.settings(), this.model(target));
|
|
15074
15450
|
}
|
|
15075
15451
|
selectable(connectionId) {
|
|
15076
15452
|
return isSelectable(this.settings().connections.get(connectionId));
|
|
15077
15453
|
}
|
|
15454
|
+
model(target) {
|
|
15455
|
+
return target.kind === "live" ? nextModel(this.ctx, target.agent) : void 0;
|
|
15456
|
+
}
|
|
15078
15457
|
async table() {
|
|
15079
15458
|
return (await this.storage.domain()).table("selections");
|
|
15080
15459
|
}
|
|
15081
15460
|
exclusive(id, work) {
|
|
15082
|
-
|
|
15083
|
-
const run = previous === void 0 ? work() : previous.then(work);
|
|
15084
|
-
const tail = run.then(() => {}, () => {});
|
|
15085
|
-
this.queues.set(id, tail);
|
|
15086
|
-
tail.then(() => {
|
|
15087
|
-
if (this.queues.get(id) === tail) this.queues.delete(id);
|
|
15088
|
-
});
|
|
15089
|
-
return run;
|
|
15461
|
+
return this.storage.exclusive(id, work);
|
|
15090
15462
|
}
|
|
15091
|
-
async read(
|
|
15463
|
+
async read(target, table, persist = true) {
|
|
15092
15464
|
const settings = this.settings();
|
|
15093
15465
|
const codec = this.storage.optionsCodec;
|
|
15094
|
-
const id =
|
|
15466
|
+
const id = target.id;
|
|
15095
15467
|
let stored = table.get(id);
|
|
15096
|
-
const
|
|
15097
|
-
const
|
|
15468
|
+
const origin = sessionOrigin(target.header);
|
|
15469
|
+
const parentSession = parentOf(origin);
|
|
15470
|
+
const seeded = parentSession !== void 0 && origin.seeded;
|
|
15098
15471
|
let inherited = stored !== void 0 && seeded && (stored.inherited ?? table.get(parentSession) !== void 0);
|
|
15099
15472
|
if (stored === void 0 && seeded) {
|
|
15100
15473
|
const parentAgent = this.ctx.get("agents")?.get(parentSession);
|
|
15101
|
-
if (parentAgent !== void 0 && persist) await this.current(parentAgent);
|
|
15102
|
-
else await this.
|
|
15474
|
+
if (parentAgent !== void 0 && persist) await this.current(liveSession(parentAgent));
|
|
15475
|
+
else await this.storage.settled(parentSession);
|
|
15103
15476
|
const parent = table.get(parentSession);
|
|
15104
15477
|
if (parent !== void 0) {
|
|
15105
15478
|
stored = {
|
|
@@ -15122,7 +15495,7 @@ var Sessions = class {
|
|
|
15122
15495
|
source: next.revision > 0 ? "session" : "inherited"
|
|
15123
15496
|
};
|
|
15124
15497
|
}
|
|
15125
|
-
const fallback = await this.defaultFor(
|
|
15498
|
+
const fallback = await this.defaultFor(target, settings);
|
|
15126
15499
|
const options = stored === void 0 ? codec.initial(settings) : codec.fromRecord(stored, settings);
|
|
15127
15500
|
const next = {
|
|
15128
15501
|
...codec.toRecord(options),
|
|
@@ -15136,8 +15509,8 @@ var Sessions = class {
|
|
|
15136
15509
|
source: fallback.source
|
|
15137
15510
|
};
|
|
15138
15511
|
}
|
|
15139
|
-
async defaultFor(
|
|
15140
|
-
const model =
|
|
15512
|
+
async defaultFor(target, settings) {
|
|
15513
|
+
const model = this.model(target);
|
|
15141
15514
|
const paired = model === void 0 ? void 0 : await this.pairings.get(model);
|
|
15142
15515
|
if (paired === null) return {
|
|
15143
15516
|
connectionId: null,
|
|
@@ -15164,6 +15537,24 @@ function sameRecord(left, right) {
|
|
|
15164
15537
|
return left !== void 0 && JSON.stringify(left) === JSON.stringify(right);
|
|
15165
15538
|
}
|
|
15166
15539
|
//#endregion
|
|
15540
|
+
//#region ../core/src/host/selection-write.ts
|
|
15541
|
+
/** The shared eligibility check and revisioned write for commands and Remote set. */
|
|
15542
|
+
async function writeSelection(sessions, target, change, expectedRevision) {
|
|
15543
|
+
if (typeof change.connectionId === "string" && !sessions.selectable(change.connectionId)) return {
|
|
15544
|
+
kind: "unavailable",
|
|
15545
|
+
connectionId: change.connectionId
|
|
15546
|
+
};
|
|
15547
|
+
try {
|
|
15548
|
+
return {
|
|
15549
|
+
kind: "written",
|
|
15550
|
+
current: await sessions.change(target, change, expectedRevision)
|
|
15551
|
+
};
|
|
15552
|
+
} catch (error) {
|
|
15553
|
+
if (error instanceof SelectionConflict) return { kind: "conflict" };
|
|
15554
|
+
throw error;
|
|
15555
|
+
}
|
|
15556
|
+
}
|
|
15557
|
+
//#endregion
|
|
15167
15558
|
//#region ../core/src/host/remote.ts
|
|
15168
15559
|
var __runInitializers = function(thisArg, initializers, value) {
|
|
15169
15560
|
var useValue = arguments.length > 2;
|
|
@@ -15311,27 +15702,22 @@ function createConnectionsService(ctx, spec) {
|
|
|
15311
15702
|
async get(request) {
|
|
15312
15703
|
return boundary("get", async () => {
|
|
15313
15704
|
const { sessionId } = parse(selectionRequest, request);
|
|
15314
|
-
const
|
|
15315
|
-
return this.view(
|
|
15705
|
+
const target = await this.target(sessionId);
|
|
15706
|
+
return this.view(target, await spec.sessions.current(target));
|
|
15316
15707
|
});
|
|
15317
15708
|
}
|
|
15318
15709
|
async set(request) {
|
|
15319
15710
|
return boundary("set", async () => {
|
|
15320
15711
|
const { sessionId, connectionId, options, expectedRevision } = parse(spec.contract.selectionChange, request);
|
|
15321
|
-
const
|
|
15322
|
-
|
|
15323
|
-
|
|
15324
|
-
|
|
15325
|
-
|
|
15326
|
-
|
|
15327
|
-
|
|
15328
|
-
|
|
15329
|
-
|
|
15330
|
-
} catch (error) {
|
|
15331
|
-
if (error instanceof SelectionConflict) throw new RemoteError(spec.namespace + "/conflict", "the selection changed elsewhere; re-read it and retry", {});
|
|
15332
|
-
throw error;
|
|
15333
|
-
}
|
|
15334
|
-
return this.view(agent, current);
|
|
15712
|
+
const target = await this.target(sessionId);
|
|
15713
|
+
const change = {
|
|
15714
|
+
...options,
|
|
15715
|
+
...connectionId === void 0 ? {} : { connectionId }
|
|
15716
|
+
};
|
|
15717
|
+
const result = await writeSelection(spec.sessions, target, change, expectedRevision);
|
|
15718
|
+
if (result.kind === "unavailable") throw new RemoteError(spec.namespace + "/unavailable", "the connection is missing, disabled or unsupported", { connectionId: result.connectionId });
|
|
15719
|
+
if (result.kind === "conflict") throw new RemoteError(spec.namespace + "/conflict", "the selection changed elsewhere; re-read it and retry", {});
|
|
15720
|
+
return this.view(target, result.current);
|
|
15335
15721
|
});
|
|
15336
15722
|
}
|
|
15337
15723
|
async provider(request) {
|
|
@@ -15353,15 +15739,27 @@ function createConnectionsService(ctx, spec) {
|
|
|
15353
15739
|
return { pairings: await spec.pairings.list() };
|
|
15354
15740
|
});
|
|
15355
15741
|
}
|
|
15356
|
-
async
|
|
15357
|
-
const
|
|
15742
|
+
async target(sessionId) {
|
|
15743
|
+
const id = sessionId;
|
|
15744
|
+
const agent = this.ctx.agents.get(id);
|
|
15745
|
+
if (agent !== void 0) return liveSession(agent);
|
|
15746
|
+
const persistence = this.ctx.get("sessionPersistence");
|
|
15747
|
+
if (persistence !== void 0) {
|
|
15748
|
+
const stored = await persistence.stat(id);
|
|
15749
|
+
if (stored !== void 0 && sessionOrigin(stored.header).kind === "subagent") return {
|
|
15750
|
+
kind: "cold",
|
|
15751
|
+
id,
|
|
15752
|
+
header: stored.header
|
|
15753
|
+
};
|
|
15754
|
+
}
|
|
15755
|
+
const found = await this.ctx.sessionController.resolveAgent(id);
|
|
15358
15756
|
if ("error" in found) throw found.error;
|
|
15359
|
-
return found.agent;
|
|
15757
|
+
return liveSession(found.agent);
|
|
15360
15758
|
}
|
|
15361
|
-
async view(
|
|
15759
|
+
async view(target, current) {
|
|
15362
15760
|
return {
|
|
15363
15761
|
...current,
|
|
15364
|
-
connections: await spec.sessions.connections(
|
|
15762
|
+
connections: await spec.sessions.connections(target)
|
|
15365
15763
|
};
|
|
15366
15764
|
}
|
|
15367
15765
|
};
|
|
@@ -15369,55 +15767,210 @@ function createConnectionsService(ctx, spec) {
|
|
|
15369
15767
|
}
|
|
15370
15768
|
//#endregion
|
|
15371
15769
|
//#region ../core/src/host/restriction.ts
|
|
15372
|
-
/**
|
|
15770
|
+
/** Owns registration and both masks, including their single ordered teardown. */
|
|
15373
15771
|
var ToolRestriction = class {
|
|
15772
|
+
ctx;
|
|
15374
15773
|
options;
|
|
15375
15774
|
lifts = /* @__PURE__ */ new Map();
|
|
15775
|
+
enhancedLifts = /* @__PURE__ */ new Map();
|
|
15376
15776
|
known = /* @__PURE__ */ new Map();
|
|
15777
|
+
states = /* @__PURE__ */ new Map();
|
|
15377
15778
|
forgotten = /* @__PURE__ */ new WeakSet();
|
|
15779
|
+
removes = [];
|
|
15780
|
+
definitions;
|
|
15781
|
+
generation = 0;
|
|
15378
15782
|
disposed = false;
|
|
15783
|
+
enhancedContext;
|
|
15379
15784
|
constructor(ctx, options) {
|
|
15785
|
+
this.ctx = ctx;
|
|
15380
15786
|
this.options = options;
|
|
15381
15787
|
ctx.on("settings/document-updated", async (ns) => {
|
|
15382
|
-
if (
|
|
15383
|
-
const
|
|
15384
|
-
|
|
15385
|
-
const
|
|
15386
|
-
|
|
15387
|
-
|
|
15388
|
-
|
|
15389
|
-
|
|
15788
|
+
if (this.disposed) return;
|
|
15789
|
+
const enhanced = options.enhanced;
|
|
15790
|
+
if (ns !== "web" && (enhanced === void 0 || ns !== enhanced.rowId)) return;
|
|
15791
|
+
const newlyRegistered = this.reconcile();
|
|
15792
|
+
const nativeAgents = ns === "web" ? [...this.known.keys()] : [];
|
|
15793
|
+
if (!options.routed()) for (const agent of nativeAgents) this.native(agent, this.known.get(agent));
|
|
15794
|
+
const agents = new Set(newlyRegistered);
|
|
15795
|
+
if (this.registered) for (const agent of this.states.keys()) agents.add(agent);
|
|
15796
|
+
if (options.routed()) for (const agent of nativeAgents) agents.add(agent);
|
|
15797
|
+
await Promise.all([...agents].map((agent) => this.refresh(agent, nativeAgents.includes(agent))));
|
|
15390
15798
|
});
|
|
15391
15799
|
ctx.effect(() => () => {
|
|
15392
15800
|
this.disposed = true;
|
|
15393
15801
|
for (const lift of this.lifts.values()) lift();
|
|
15394
15802
|
this.lifts.clear();
|
|
15395
15803
|
this.known.clear();
|
|
15396
|
-
|
|
15804
|
+
this.states.clear();
|
|
15805
|
+
}, options.tool + " visibility");
|
|
15397
15806
|
}
|
|
15398
15807
|
get active() {
|
|
15399
15808
|
return !this.disposed;
|
|
15400
15809
|
}
|
|
15810
|
+
get registered() {
|
|
15811
|
+
return this.removes.length !== 0;
|
|
15812
|
+
}
|
|
15813
|
+
offered() {
|
|
15814
|
+
return this.active && this.enhancedContext !== void 0 && this.options.routed() && this.options.enhanced !== void 0 && this.options.enhanced.enabled();
|
|
15815
|
+
}
|
|
15816
|
+
/** Call only after all runtime closures exist. Hot-start reads never create records. */
|
|
15817
|
+
async start() {
|
|
15818
|
+
if (this.options.enhanced === void 0) return;
|
|
15819
|
+
await this.ctx.inject(["tools", "agents"], async (scope) => {
|
|
15820
|
+
this.enhancedContext = scope;
|
|
15821
|
+
scope.effect(() => () => {
|
|
15822
|
+
this.withdraw();
|
|
15823
|
+
this.enhancedContext = void 0;
|
|
15824
|
+
}, this.options.tool + " enhanced visibility");
|
|
15825
|
+
const agents = this.reconcile();
|
|
15826
|
+
await Promise.all(agents.map((agent) => this.refresh(agent, false)));
|
|
15827
|
+
});
|
|
15828
|
+
}
|
|
15829
|
+
created(agent) {
|
|
15830
|
+
if (!this.active || this.forgotten.has(agent)) return;
|
|
15831
|
+
this.states.set(agent, {
|
|
15832
|
+
version: 0,
|
|
15833
|
+
choice: "unknown",
|
|
15834
|
+
providers: "unknown"
|
|
15835
|
+
});
|
|
15836
|
+
this.apply(agent);
|
|
15837
|
+
}
|
|
15838
|
+
begin(agent) {
|
|
15839
|
+
const state = this.state(agent);
|
|
15840
|
+
state.version++;
|
|
15841
|
+
return {
|
|
15842
|
+
generation: this.generation,
|
|
15843
|
+
version: state.version
|
|
15844
|
+
};
|
|
15845
|
+
}
|
|
15846
|
+
captured(agent, read, connectionId, providers) {
|
|
15847
|
+
this.commit(agent, read, connectionId === null ? "off" : "on", this.providerState(providers), connectionId);
|
|
15848
|
+
}
|
|
15849
|
+
providerState(read) {
|
|
15850
|
+
if (read.kind === "unread") return "unknown";
|
|
15851
|
+
return read.ids === void 0 ? "absent" : "present";
|
|
15852
|
+
}
|
|
15853
|
+
failed(agent, read) {
|
|
15854
|
+
this.commit(agent, read, "unknown", "unknown");
|
|
15855
|
+
}
|
|
15401
15856
|
sync(agent, connectionId) {
|
|
15402
|
-
if (this.
|
|
15403
|
-
this.
|
|
15404
|
-
|
|
15857
|
+
if (!this.active || this.forgotten.has(agent)) return;
|
|
15858
|
+
const read = this.begin(agent);
|
|
15859
|
+
this.commit(agent, read, connectionId === null ? "off" : "on", this.state(agent).providers, connectionId);
|
|
15860
|
+
}
|
|
15861
|
+
forget(agent) {
|
|
15862
|
+
this.forgotten.add(agent);
|
|
15863
|
+
this.known.delete(agent);
|
|
15864
|
+
this.states.delete(agent);
|
|
15865
|
+
this.lift(this.lifts, agent);
|
|
15866
|
+
this.lift(this.enhancedLifts, agent);
|
|
15867
|
+
}
|
|
15868
|
+
state(agent) {
|
|
15869
|
+
let state = this.states.get(agent);
|
|
15870
|
+
if (state === void 0) {
|
|
15871
|
+
state = {
|
|
15872
|
+
version: 0,
|
|
15873
|
+
choice: "unknown",
|
|
15874
|
+
providers: "unknown"
|
|
15875
|
+
};
|
|
15876
|
+
this.states.set(agent, state);
|
|
15877
|
+
}
|
|
15878
|
+
return state;
|
|
15879
|
+
}
|
|
15880
|
+
reconcile() {
|
|
15881
|
+
if (!this.offered()) {
|
|
15882
|
+
if (this.registered) this.withdraw();
|
|
15883
|
+
return [];
|
|
15884
|
+
}
|
|
15885
|
+
if (this.registered) return [];
|
|
15886
|
+
const enhanced = this.options.enhanced;
|
|
15887
|
+
if (this.definitions === void 0) this.definitions = enhanced.tools();
|
|
15888
|
+
this.generation++;
|
|
15889
|
+
const scope = this.enhancedContext;
|
|
15890
|
+
const removes = [];
|
|
15891
|
+
try {
|
|
15892
|
+
removes.push(scope.tools.register(this.definitions.action));
|
|
15893
|
+
removes.push(scope.tools.register(this.definitions.list));
|
|
15894
|
+
} catch {
|
|
15895
|
+
for (const remove of removes.toReversed()) remove();
|
|
15896
|
+
this.ctx.logger.warn("%s: enhanced tool registration failed", this.options.tool);
|
|
15897
|
+
return [];
|
|
15898
|
+
}
|
|
15899
|
+
this.removes = removes;
|
|
15900
|
+
const agents = scope.agents.list();
|
|
15901
|
+
for (const agent of agents) {
|
|
15902
|
+
this.state(agent);
|
|
15903
|
+
this.apply(agent);
|
|
15904
|
+
}
|
|
15905
|
+
return agents;
|
|
15906
|
+
}
|
|
15907
|
+
withdraw() {
|
|
15908
|
+
this.generation++;
|
|
15909
|
+
for (const remove of this.removes.splice(0)) remove();
|
|
15910
|
+
for (const lift of this.enhancedLifts.values()) lift();
|
|
15911
|
+
this.enhancedLifts.clear();
|
|
15912
|
+
}
|
|
15913
|
+
async refresh(agent, native) {
|
|
15914
|
+
if (!this.active || this.forgotten.has(agent)) return;
|
|
15915
|
+
const read = this.begin(agent);
|
|
15916
|
+
try {
|
|
15917
|
+
const selection = this.options.selection;
|
|
15918
|
+
const connectionId = selection === void 0 ? this.known.get(agent) : await selection(agent);
|
|
15919
|
+
if (connectionId === void 0) return;
|
|
15920
|
+
const enhanced = this.options.enhanced;
|
|
15921
|
+
let providers = { kind: "unread" };
|
|
15922
|
+
if (this.registered && this.offered() && enhanced !== void 0) try {
|
|
15923
|
+
providers = {
|
|
15924
|
+
kind: "read",
|
|
15925
|
+
ids: await enhanced.providers(agent)
|
|
15926
|
+
};
|
|
15927
|
+
} catch {
|
|
15928
|
+
this.ctx.logger.warn("%s: conversation provider list could not be read", this.options.tool);
|
|
15929
|
+
}
|
|
15930
|
+
this.commit(agent, read, connectionId === null ? "off" : "on", this.providerState(providers), native ? connectionId : void 0);
|
|
15931
|
+
} catch (error) {
|
|
15932
|
+
this.failed(agent, read);
|
|
15933
|
+
this.ctx.logger.warn("%s: refreshing tool restriction failed: %s", this.options.tool, error);
|
|
15934
|
+
}
|
|
15935
|
+
}
|
|
15936
|
+
commit(agent, read, choice, providers, native) {
|
|
15937
|
+
const state = this.states.get(agent);
|
|
15938
|
+
if (!this.active || this.forgotten.has(agent) || state === void 0 || read.generation !== this.generation || read.version !== state.version) return;
|
|
15939
|
+
if (this.registered && !this.offered()) {
|
|
15940
|
+
this.withdraw();
|
|
15941
|
+
return;
|
|
15942
|
+
}
|
|
15943
|
+
state.choice = choice;
|
|
15944
|
+
state.providers = providers;
|
|
15945
|
+
if (native !== void 0) {
|
|
15946
|
+
this.known.set(agent, native);
|
|
15947
|
+
this.native(agent, native);
|
|
15948
|
+
}
|
|
15949
|
+
this.apply(agent);
|
|
15950
|
+
}
|
|
15951
|
+
apply(agent) {
|
|
15952
|
+
if (!this.registered) return;
|
|
15953
|
+
const state = this.state(agent);
|
|
15954
|
+
if (state.choice === "on" && state.providers === "present") this.lift(this.enhancedLifts, agent);
|
|
15955
|
+
else if (!this.enhancedLifts.has(agent)) {
|
|
15956
|
+
const names = this.options.enhanced.names;
|
|
15957
|
+
this.enhancedLifts.set(agent, agent.ctx.tools.restrict({ deny: [names.action, names.list] }));
|
|
15958
|
+
}
|
|
15959
|
+
}
|
|
15960
|
+
native(agent, connectionId) {
|
|
15405
15961
|
if (connectionId !== null || !this.options.routed()) {
|
|
15406
|
-
this.lifts
|
|
15407
|
-
if (lift !== void 0) lift();
|
|
15962
|
+
this.lift(this.lifts, agent);
|
|
15408
15963
|
return;
|
|
15409
15964
|
}
|
|
15410
|
-
if (
|
|
15965
|
+
if (this.lifts.has(agent)) return;
|
|
15411
15966
|
const tools = agent.ctx.get("tools");
|
|
15412
15967
|
const scope = scopeOf(agent.ctx);
|
|
15413
15968
|
if (scope === void 0 || tools === void 0 || tools.get(this.options.tool, scope) === void 0) return;
|
|
15414
15969
|
this.lifts.set(agent, tools.restrict({ deny: [this.options.tool] }));
|
|
15415
15970
|
}
|
|
15416
|
-
|
|
15417
|
-
|
|
15418
|
-
|
|
15419
|
-
const lift = this.lifts.get(agent);
|
|
15420
|
-
this.lifts.delete(agent);
|
|
15971
|
+
lift(lifts, agent) {
|
|
15972
|
+
const lift = lifts.get(agent);
|
|
15973
|
+
lifts.delete(agent);
|
|
15421
15974
|
if (lift !== void 0) lift();
|
|
15422
15975
|
}
|
|
15423
15976
|
};
|
|
@@ -15438,6 +15991,207 @@ function routeFacts(ctx, field, providerId) {
|
|
|
15438
15991
|
return routeOf(value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0, field, environment(field), providerId);
|
|
15439
15992
|
}
|
|
15440
15993
|
//#endregion
|
|
15994
|
+
//#region ../core/src/host/explicit.ts
|
|
15995
|
+
const rejected = (reason) => ({
|
|
15996
|
+
kind: "rejected",
|
|
15997
|
+
reason
|
|
15998
|
+
});
|
|
15999
|
+
function explicitAccess(ctx, capability, runtime, errors, unavailable) {
|
|
16000
|
+
const availabilityError = () => errors.error("WEB_PROVIDER_ERROR", "provider availability could not be read; retry the request");
|
|
16001
|
+
const diagnostic = () => {
|
|
16002
|
+
ctx.logger.warn("%s: provider availability could not be read", capability.identity.rowId);
|
|
16003
|
+
};
|
|
16004
|
+
function check() {
|
|
16005
|
+
if (!runtime.active()) return rejected({
|
|
16006
|
+
kind: "unavailable",
|
|
16007
|
+
error: unavailable()
|
|
16008
|
+
});
|
|
16009
|
+
let settings;
|
|
16010
|
+
try {
|
|
16011
|
+
settings = capability.settings();
|
|
16012
|
+
} catch (error) {
|
|
16013
|
+
return rejected({
|
|
16014
|
+
kind: "unavailable",
|
|
16015
|
+
error: error instanceof WebError ? error : unavailable()
|
|
16016
|
+
});
|
|
16017
|
+
}
|
|
16018
|
+
if (!runtime.offered()) return rejected({ kind: "withdrawn" });
|
|
16019
|
+
return {
|
|
16020
|
+
kind: "done",
|
|
16021
|
+
value: settings
|
|
16022
|
+
};
|
|
16023
|
+
}
|
|
16024
|
+
async function prepare(call) {
|
|
16025
|
+
errors.throwIfAborted(call.signal);
|
|
16026
|
+
const checked = check();
|
|
16027
|
+
if (checked.kind === "rejected") return checked;
|
|
16028
|
+
const plan = runtime.plan(call.agent);
|
|
16029
|
+
if (plan === void 0) return rejected({
|
|
16030
|
+
kind: "unavailable",
|
|
16031
|
+
error: unavailable()
|
|
16032
|
+
});
|
|
16033
|
+
if (plan.kind === "failed") return rejected({
|
|
16034
|
+
kind: "unavailable",
|
|
16035
|
+
error: plan.error
|
|
16036
|
+
});
|
|
16037
|
+
if (plan.kind === "off") return rejected({ kind: "off" });
|
|
16038
|
+
let ids;
|
|
16039
|
+
try {
|
|
16040
|
+
ids = await errors.abortable(runtime.providers(call.agent), call.signal);
|
|
16041
|
+
} catch {
|
|
16042
|
+
errors.throwIfAborted(call.signal);
|
|
16043
|
+
ctx.logger.warn("%s: conversation provider list could not be read", capability.identity.rowId);
|
|
16044
|
+
return rejected({
|
|
16045
|
+
kind: "unavailable",
|
|
16046
|
+
error: unavailable()
|
|
16047
|
+
});
|
|
16048
|
+
}
|
|
16049
|
+
errors.throwIfAborted(call.signal);
|
|
16050
|
+
const current = check();
|
|
16051
|
+
if (current.kind === "rejected") return current;
|
|
16052
|
+
if (ids === void 0) return rejected({ kind: "withdrawn" });
|
|
16053
|
+
return {
|
|
16054
|
+
kind: "done",
|
|
16055
|
+
value: {
|
|
16056
|
+
settings: current.value,
|
|
16057
|
+
plan,
|
|
16058
|
+
ids
|
|
16059
|
+
}
|
|
16060
|
+
};
|
|
16061
|
+
}
|
|
16062
|
+
async function views(prepared, call) {
|
|
16063
|
+
errors.throwIfAborted(call.signal);
|
|
16064
|
+
const connections = /* @__PURE__ */ new Map();
|
|
16065
|
+
for (const id of prepared.ids) {
|
|
16066
|
+
const connection = prepared.settings.connections.get(id);
|
|
16067
|
+
if (connection !== void 0) connections.set(id, {
|
|
16068
|
+
...connection,
|
|
16069
|
+
disabled: false
|
|
16070
|
+
});
|
|
16071
|
+
}
|
|
16072
|
+
try {
|
|
16073
|
+
const described = await errors.abortable(capability.describe({
|
|
16074
|
+
...prepared.settings,
|
|
16075
|
+
connections
|
|
16076
|
+
}, nextModel(ctx, call.agent)), call.signal);
|
|
16077
|
+
errors.throwIfAborted(call.signal);
|
|
16078
|
+
return new Map(described.map((view) => [view.id, view]));
|
|
16079
|
+
} catch {
|
|
16080
|
+
errors.throwIfAborted(call.signal);
|
|
16081
|
+
diagnostic();
|
|
16082
|
+
throw availabilityError();
|
|
16083
|
+
}
|
|
16084
|
+
}
|
|
16085
|
+
function readiness(view) {
|
|
16086
|
+
if (view === void 0) throw availabilityError();
|
|
16087
|
+
switch (view.status) {
|
|
16088
|
+
case "ready": return { status: "ready" };
|
|
16089
|
+
case "needs-credential": return {
|
|
16090
|
+
status: "needs-credential",
|
|
16091
|
+
credential: view.access === "account" ? "account" : "api-key"
|
|
16092
|
+
};
|
|
16093
|
+
case "unsupported":
|
|
16094
|
+
if (view.reason !== void 0) return {
|
|
16095
|
+
status: "unsupported",
|
|
16096
|
+
reason: view.reason
|
|
16097
|
+
};
|
|
16098
|
+
throw availabilityError();
|
|
16099
|
+
case "disabled": throw availabilityError();
|
|
16100
|
+
}
|
|
16101
|
+
}
|
|
16102
|
+
async function readyPrepared(prepared, call) {
|
|
16103
|
+
try {
|
|
16104
|
+
const described = await views(prepared, call);
|
|
16105
|
+
errors.throwIfAborted(call.signal);
|
|
16106
|
+
return {
|
|
16107
|
+
kind: "read",
|
|
16108
|
+
ids: prepared.ids.filter((id) => prepared.settings.connections.has(id) && described.get(id)?.status === "ready")
|
|
16109
|
+
};
|
|
16110
|
+
} catch {
|
|
16111
|
+
return { kind: "unread" };
|
|
16112
|
+
}
|
|
16113
|
+
}
|
|
16114
|
+
return {
|
|
16115
|
+
async run(connectionId, request, call) {
|
|
16116
|
+
const result = await prepare(call);
|
|
16117
|
+
errors.throwIfAborted(call.signal);
|
|
16118
|
+
if (result.kind === "rejected") return result;
|
|
16119
|
+
const prepared = result.value;
|
|
16120
|
+
const connection = prepared.settings.connections.get(connectionId);
|
|
16121
|
+
const refusal = !prepared.ids.includes(connectionId) ? "unknown" : connection === void 0 ? "removed" : void 0;
|
|
16122
|
+
if (refusal !== void 0) {
|
|
16123
|
+
const ready = await readyPrepared(prepared, call);
|
|
16124
|
+
errors.throwIfAborted(call.signal);
|
|
16125
|
+
return rejected({
|
|
16126
|
+
kind: refusal,
|
|
16127
|
+
connectionId,
|
|
16128
|
+
ready
|
|
16129
|
+
});
|
|
16130
|
+
}
|
|
16131
|
+
return ctx.get("agents").withInitiator(call.agent, async () => {
|
|
16132
|
+
errors.throwIfAborted(call.signal);
|
|
16133
|
+
const execution = {
|
|
16134
|
+
ctx,
|
|
16135
|
+
agent: call.agent,
|
|
16136
|
+
signal: call.signal
|
|
16137
|
+
};
|
|
16138
|
+
const target = capability.target(connection, prepared.plan.options, execution);
|
|
16139
|
+
return {
|
|
16140
|
+
kind: "done",
|
|
16141
|
+
value: await capability.execute(target, request, execution)
|
|
16142
|
+
};
|
|
16143
|
+
});
|
|
16144
|
+
},
|
|
16145
|
+
async list(call) {
|
|
16146
|
+
const result = await prepare(call);
|
|
16147
|
+
errors.throwIfAborted(call.signal);
|
|
16148
|
+
if (result.kind === "rejected") return result;
|
|
16149
|
+
const prepared = result.value;
|
|
16150
|
+
const described = await views(prepared, call);
|
|
16151
|
+
errors.throwIfAborted(call.signal);
|
|
16152
|
+
try {
|
|
16153
|
+
const providers = prepared.ids.flatMap((id) => {
|
|
16154
|
+
const connection = prepared.settings.connections.get(id);
|
|
16155
|
+
if (connection === void 0) return [];
|
|
16156
|
+
const view = described.get(id);
|
|
16157
|
+
const state = readiness(view);
|
|
16158
|
+
return [{
|
|
16159
|
+
id,
|
|
16160
|
+
name: view.label,
|
|
16161
|
+
facts: capability.enhanced.listing(connection, call),
|
|
16162
|
+
...state
|
|
16163
|
+
}];
|
|
16164
|
+
});
|
|
16165
|
+
errors.throwIfAborted(call.signal);
|
|
16166
|
+
return {
|
|
16167
|
+
kind: "done",
|
|
16168
|
+
value: {
|
|
16169
|
+
session: {
|
|
16170
|
+
connectionId: prepared.plan.connectionId,
|
|
16171
|
+
selectable: isSelectable(prepared.settings.connections.get(prepared.plan.connectionId))
|
|
16172
|
+
},
|
|
16173
|
+
options: prepared.plan.options,
|
|
16174
|
+
providers
|
|
16175
|
+
}
|
|
16176
|
+
};
|
|
16177
|
+
} catch {
|
|
16178
|
+
errors.throwIfAborted(call.signal);
|
|
16179
|
+
diagnostic();
|
|
16180
|
+
throw availabilityError();
|
|
16181
|
+
}
|
|
16182
|
+
},
|
|
16183
|
+
async ready(call) {
|
|
16184
|
+
try {
|
|
16185
|
+
const prepared = await prepare(call);
|
|
16186
|
+
errors.throwIfAborted(call.signal);
|
|
16187
|
+
return prepared.kind === "rejected" ? { kind: "unread" } : await readyPrepared(prepared.value, call);
|
|
16188
|
+
} catch {
|
|
16189
|
+
return { kind: "unread" };
|
|
16190
|
+
}
|
|
16191
|
+
}
|
|
16192
|
+
};
|
|
16193
|
+
}
|
|
16194
|
+
//#endregion
|
|
15441
16195
|
//#region ../core/src/host/storage.ts
|
|
15442
16196
|
function selectionDomain(name) {
|
|
15443
16197
|
const selection = looseObject({
|
|
@@ -15451,7 +16205,8 @@ function selectionDomain(name) {
|
|
|
15451
16205
|
version: 1,
|
|
15452
16206
|
tables: {
|
|
15453
16207
|
selections: domainTable(selection),
|
|
15454
|
-
pairings: domainTable(pairing)
|
|
16208
|
+
pairings: domainTable(pairing),
|
|
16209
|
+
provider_lists: domainTable(array(string$1()))
|
|
15455
16210
|
}
|
|
15456
16211
|
});
|
|
15457
16212
|
}
|
|
@@ -15492,6 +16247,7 @@ function openDomain(ctx, name, unavailable = () => capabilityErrors(name).error(
|
|
|
15492
16247
|
var SessionStorage = class {
|
|
15493
16248
|
optionsCodec;
|
|
15494
16249
|
access;
|
|
16250
|
+
queues = /* @__PURE__ */ new Map();
|
|
15495
16251
|
constructor(ctx, name, optionsCodec, unavailable) {
|
|
15496
16252
|
this.optionsCodec = optionsCodec;
|
|
15497
16253
|
this.access = openDomain(ctx, name, unavailable);
|
|
@@ -15499,7 +16255,202 @@ var SessionStorage = class {
|
|
|
15499
16255
|
domain() {
|
|
15500
16256
|
return this.access.domain();
|
|
15501
16257
|
}
|
|
16258
|
+
exclusive(id, work) {
|
|
16259
|
+
const previous = this.queues.get(id);
|
|
16260
|
+
const run = previous === void 0 ? work() : previous.then(work);
|
|
16261
|
+
const tail = run.then(() => {}, () => {});
|
|
16262
|
+
this.queues.set(id, tail);
|
|
16263
|
+
tail.then(() => {
|
|
16264
|
+
if (this.queues.get(id) === tail) this.queues.delete(id);
|
|
16265
|
+
});
|
|
16266
|
+
return run;
|
|
16267
|
+
}
|
|
16268
|
+
settled(id) {
|
|
16269
|
+
return this.queues.get(id);
|
|
16270
|
+
}
|
|
16271
|
+
};
|
|
16272
|
+
//#endregion
|
|
16273
|
+
//#region ../core/src/host/command.ts
|
|
16274
|
+
var SelectionOperationError = class extends Error {};
|
|
16275
|
+
const SOURCES = {
|
|
16276
|
+
session: "chosen in this session",
|
|
16277
|
+
inherited: "copied from the parent session",
|
|
16278
|
+
model: "remembered for this model",
|
|
16279
|
+
default: "the default connection"
|
|
15502
16280
|
};
|
|
16281
|
+
function registerCommand(ctx, capability, sessions) {
|
|
16282
|
+
const { command, providerId, rowId } = capability.identity;
|
|
16283
|
+
const noun = capability.tool === "web_search" ? "web search" : "web fetch";
|
|
16284
|
+
const capital = capability.tool === "web_search" ? "Web search" : "Web fetch";
|
|
16285
|
+
const options = capability.commandOptions;
|
|
16286
|
+
const hint = options === void 0 ? "[off | <connection id>]" : "[off | <connection id> | freshness auto|fresh|realtime]";
|
|
16287
|
+
const failure = "The " + noun + " selection could not be read or saved; retry, or check the Host logs.";
|
|
16288
|
+
const error = (text) => ({
|
|
16289
|
+
kind: "error",
|
|
16290
|
+
text
|
|
16291
|
+
});
|
|
16292
|
+
const selectionOperation = async (work) => {
|
|
16293
|
+
try {
|
|
16294
|
+
return await work();
|
|
16295
|
+
} catch (cause) {
|
|
16296
|
+
if (cause instanceof InvalidSettingsError) throw cause;
|
|
16297
|
+
ctx.logger.warn("%s command selection read or write failed: %s", rowId, cause);
|
|
16298
|
+
throw new SelectionOperationError("selection read or write failed");
|
|
16299
|
+
}
|
|
16300
|
+
};
|
|
16301
|
+
const route = () => routeFacts(ctx, capability.field, providerId);
|
|
16302
|
+
const routing = () => routingText(route(), capability.tool, providerId);
|
|
16303
|
+
const unavailable = (id) => error(id + " is missing or turned off in the settings. Run /" + command + " to list the connections.");
|
|
16304
|
+
const success = (text) => ({
|
|
16305
|
+
kind: "success",
|
|
16306
|
+
text: text + (route().kind === "this" ? "" : "\n" + routing())
|
|
16307
|
+
});
|
|
16308
|
+
const run = async (invocation) => {
|
|
16309
|
+
const raw = invocation.rawInput.trim();
|
|
16310
|
+
const args = raw === "" ? [] : raw.split(/\s+/u);
|
|
16311
|
+
const usage = () => error("Unknown argument \"" + oneLine(raw, 64) + "\". Usage: /" + command + " " + hint);
|
|
16312
|
+
const keyword = args.length === 0 ? "" : args[0].toLowerCase();
|
|
16313
|
+
const freshnessValue = args.length === 2 ? args[1].toLowerCase() : "";
|
|
16314
|
+
const freshness = options !== void 0 && args.length === 2 && keyword === "freshness";
|
|
16315
|
+
if (args.length > 0 && !freshness && !(args.length === 1 && (keyword === "off" || isConnectionId(args[0])))) return usage();
|
|
16316
|
+
if (freshness && !FRESHNESS.some((value) => value === freshnessValue)) return error("Unknown freshness \"" + oneLine(args[1], 64) + "\". Use auto, fresh or realtime.");
|
|
16317
|
+
const target = liveSession(invocation.agent);
|
|
16318
|
+
const settings = capability.settings();
|
|
16319
|
+
const current = await selectionOperation(() => sessions.current(target));
|
|
16320
|
+
if (args.length === 0) {
|
|
16321
|
+
const views = await sessions.connections(target);
|
|
16322
|
+
const id = current.selection.connectionId;
|
|
16323
|
+
const selected = id === null ? void 0 : views.find((view) => view.id === id);
|
|
16324
|
+
let summary = id === null ? "Off" : selected === void 0 || !sessions.selectable(id) ? id : oneLine(selected.label, 200) + " (" + id + ")";
|
|
16325
|
+
summary += " · " + SOURCES[current.source];
|
|
16326
|
+
if (id !== null && !sessions.selectable(id)) summary += " · missing or turned off in the settings; " + capability.tool + " fails until another connection is chosen";
|
|
16327
|
+
if (options !== void 0) summary += " · freshness: " + options.freshness(current.selection.options);
|
|
16328
|
+
const entries = [];
|
|
16329
|
+
for (const view of views) {
|
|
16330
|
+
const connection = settings.connections.get(view.id);
|
|
16331
|
+
if (!isSelectable(connection)) continue;
|
|
16332
|
+
const name = oneLine(view.label, 200);
|
|
16333
|
+
const parts = [statusText(view)];
|
|
16334
|
+
if (view.id === id) parts.push("current");
|
|
16335
|
+
if (view.freshness === true) parts.push("applies freshness");
|
|
16336
|
+
const recipient = recipientText(capability.enhanced.listing(connection, {
|
|
16337
|
+
agent: invocation.agent,
|
|
16338
|
+
signal: invocation.signal
|
|
16339
|
+
}).recipient);
|
|
16340
|
+
if (recipient === "this machine") parts.push("fetches from this machine");
|
|
16341
|
+
else if (recipient !== name) parts.push((capability.tool === "web_search" ? "queries" : "URLs") + " go to " + recipient);
|
|
16342
|
+
entries.push("- " + view.id + " — " + name + " · " + parts.join(" · "));
|
|
16343
|
+
}
|
|
16344
|
+
if (entries.length === 0) entries.push("- (none; turn connections on in the row settings)");
|
|
16345
|
+
const tail = "Turn off: /" + command + " off" + (options === void 0 ? "" : " · Freshness: /" + command + " freshness auto|fresh|realtime (connections marked \"applies freshness\")");
|
|
16346
|
+
return {
|
|
16347
|
+
kind: "success",
|
|
16348
|
+
text: [
|
|
16349
|
+
summary,
|
|
16350
|
+
routing(),
|
|
16351
|
+
"Connections (/" + command + " <id> chooses one):",
|
|
16352
|
+
...entries,
|
|
16353
|
+
tail
|
|
16354
|
+
].join("\n")
|
|
16355
|
+
};
|
|
16356
|
+
}
|
|
16357
|
+
let change;
|
|
16358
|
+
let message;
|
|
16359
|
+
if (freshness && options !== void 0) {
|
|
16360
|
+
const selected = (await sessions.connections(target)).find((view) => view.id === current.selection.connectionId);
|
|
16361
|
+
if (selected === void 0 || selected.freshness !== true || !sessions.selectable(selected.id)) return error("The current connection does not apply freshness. Choose one marked \"applies freshness\" first.");
|
|
16362
|
+
const value = FRESHNESS.find((mode) => mode === freshnessValue);
|
|
16363
|
+
change = options.changeFreshness(value);
|
|
16364
|
+
message = "Freshness is " + value + " from the next model request.";
|
|
16365
|
+
} else if (keyword === "off") {
|
|
16366
|
+
change = { connectionId: null };
|
|
16367
|
+
message = capital + " is off for this session from the next model request.";
|
|
16368
|
+
} else {
|
|
16369
|
+
const id = args[0];
|
|
16370
|
+
if (!sessions.selectable(id)) return unavailable(id);
|
|
16371
|
+
const selected = (await sessions.connections(target)).find((view) => view.id === id);
|
|
16372
|
+
change = { connectionId: id };
|
|
16373
|
+
message = "Now using " + oneLine(selected.label, 200) + " (" + id + ") for " + noun + " from the next model request.";
|
|
16374
|
+
}
|
|
16375
|
+
const result = await selectionOperation(() => writeSelection(sessions, target, change, current.selection.revision));
|
|
16376
|
+
if (result.kind === "unavailable") return unavailable(result.connectionId);
|
|
16377
|
+
if (result.kind === "conflict") return error("The selection changed while this command ran. Run /" + command + " to see it, then try again.");
|
|
16378
|
+
return success(message);
|
|
16379
|
+
};
|
|
16380
|
+
ctx.effect(() => ctx.commands.register({
|
|
16381
|
+
name: command,
|
|
16382
|
+
description: "Show or change this session's " + noun + " connection",
|
|
16383
|
+
input: { hint },
|
|
16384
|
+
async handler(invocation) {
|
|
16385
|
+
try {
|
|
16386
|
+
return await run(invocation);
|
|
16387
|
+
} catch (cause) {
|
|
16388
|
+
if (cause instanceof SelectionOperationError) return error(failure);
|
|
16389
|
+
ctx.logger.warn("%s command failed: %s", rowId, cause);
|
|
16390
|
+
if (cause instanceof InvalidSettingsError) return error("The " + noun + " settings are invalid; fix them in the Enhanced " + noun + " row settings.");
|
|
16391
|
+
throw new Error("The " + noun + " command failed; retry, or check the Host logs.");
|
|
16392
|
+
}
|
|
16393
|
+
}
|
|
16394
|
+
}));
|
|
16395
|
+
}
|
|
16396
|
+
function routingText(route, tool, providerId) {
|
|
16397
|
+
switch (route.kind) {
|
|
16398
|
+
case "this": return "Routing: " + tool + " goes to this plugin.";
|
|
16399
|
+
case "other": return "Routing: " + tool + " goes to " + oneLine(route.provider, 200) + ", so this selection has no effect until the web row routes it to " + providerId + ".";
|
|
16400
|
+
case "auto": return "Routing: the web row pins no " + tool + " provider, so this selection has no effect until it routes " + tool + " to " + providerId + ".";
|
|
16401
|
+
case "unknown": return "Routing: the web row could not be read, so this selection may have no effect.";
|
|
16402
|
+
}
|
|
16403
|
+
}
|
|
16404
|
+
function statusText(view) {
|
|
16405
|
+
switch (view.status) {
|
|
16406
|
+
case "ready": return "ready";
|
|
16407
|
+
case "needs-credential": return view.access === "account" ? "needs DeepSeek sign-in" : "needs an API key";
|
|
16408
|
+
case "unsupported": return "unavailable: " + UNSUPPORTED_REASON_TEXT[view.reason];
|
|
16409
|
+
case "disabled": return "missing or turned off in the settings";
|
|
16410
|
+
}
|
|
16411
|
+
}
|
|
16412
|
+
//#endregion
|
|
16413
|
+
//#region ../core/src/host/terminal.ts
|
|
16414
|
+
function registerTerminal(ctx, root, noun, settings, freshness) {
|
|
16415
|
+
ctx.effect(() => ctx.tuiCommandTrees.register({
|
|
16416
|
+
root,
|
|
16417
|
+
children(path) {
|
|
16418
|
+
if (path[0] !== root) return [];
|
|
16419
|
+
if (freshness && path.length === 2 && path[1] === "freshness") return [
|
|
16420
|
+
{
|
|
16421
|
+
name: "auto",
|
|
16422
|
+
description: "Auto"
|
|
16423
|
+
},
|
|
16424
|
+
{
|
|
16425
|
+
name: "fresh",
|
|
16426
|
+
description: "Within 24 hours"
|
|
16427
|
+
},
|
|
16428
|
+
{
|
|
16429
|
+
name: "realtime",
|
|
16430
|
+
description: "Live"
|
|
16431
|
+
}
|
|
16432
|
+
];
|
|
16433
|
+
if (path.length !== 1) return [];
|
|
16434
|
+
const nodes = [{
|
|
16435
|
+
name: "off",
|
|
16436
|
+
description: "Turn " + noun + " off for this session"
|
|
16437
|
+
}];
|
|
16438
|
+
try {
|
|
16439
|
+
for (const connection of settings().connections.values()) if (isSelectable(connection)) nodes.push({
|
|
16440
|
+
name: connection.id,
|
|
16441
|
+
description: oneLine(connection.label, 200)
|
|
16442
|
+
});
|
|
16443
|
+
} catch (error) {
|
|
16444
|
+
if (!(error instanceof InvalidSettingsError)) throw error;
|
|
16445
|
+
}
|
|
16446
|
+
if (freshness) nodes.push({
|
|
16447
|
+
name: "freshness",
|
|
16448
|
+
description: "Content freshness, for connections that apply it"
|
|
16449
|
+
});
|
|
16450
|
+
return nodes;
|
|
16451
|
+
}
|
|
16452
|
+
}));
|
|
16453
|
+
}
|
|
15503
16454
|
//#endregion
|
|
15504
16455
|
//#region ../core/src/host/runtime.ts
|
|
15505
16456
|
/** Install the sole Seat, session lifecycle, route-guarded restrictions and Remote service. */
|
|
@@ -15510,24 +16461,56 @@ function installCapability(ctx, capability) {
|
|
|
15510
16461
|
const invalid = () => errors.error("WEB_PROVIDER_CONFIGURED_UNAVAILABLE", "the selected " + kind + " connection is unavailable; ask the user to enable it or choose another connection");
|
|
15511
16462
|
const storage = new SessionStorage(ctx, capability.storageDomain, capability.optionsCodec, () => errors.error("WEB_PROVIDER_ERROR", kind + " selection storage is unavailable; retry or ask the user to check the DSH storage backend"));
|
|
15512
16463
|
const pairings = new Pairings(storage);
|
|
16464
|
+
const lists = new ProviderLists(storage);
|
|
16465
|
+
const snapshots = new Snapshots();
|
|
16466
|
+
const access = explicitAccess(ctx, capability, {
|
|
16467
|
+
active: () => restriction.active,
|
|
16468
|
+
offered: () => restriction.offered(),
|
|
16469
|
+
plan: (agent) => snapshots.get(agent),
|
|
16470
|
+
providers: (agent) => lists.read(agent)
|
|
16471
|
+
}, errors, contextUnavailable);
|
|
15513
16472
|
const restriction = new ToolRestriction(ctx, {
|
|
15514
16473
|
tool: capability.tool,
|
|
15515
16474
|
routed: () => routeFacts(ctx, capability.field, capability.identity.providerId).kind === "this",
|
|
15516
|
-
selection: async (agent) => (await sessions.peek(agent)).selection.connectionId
|
|
16475
|
+
selection: async (agent) => (await sessions.peek(liveSession(agent))).selection.connectionId,
|
|
16476
|
+
enhanced: {
|
|
16477
|
+
rowId: capability.identity.rowId,
|
|
16478
|
+
names: capability.enhanced.names,
|
|
16479
|
+
enabled: () => {
|
|
16480
|
+
try {
|
|
16481
|
+
return capability.settings().enhancedTools;
|
|
16482
|
+
} catch (error) {
|
|
16483
|
+
if (error instanceof InvalidSettingsError) return false;
|
|
16484
|
+
throw error;
|
|
16485
|
+
}
|
|
16486
|
+
},
|
|
16487
|
+
tools: () => capability.enhanced.tools(access),
|
|
16488
|
+
providers: (agent) => lists.read(agent)
|
|
16489
|
+
}
|
|
15517
16490
|
});
|
|
15518
|
-
const sessions = new Sessions(ctx, () => capability.settings(), storage, pairings, restriction, (settings,
|
|
15519
|
-
const snapshots = new Snapshots();
|
|
16491
|
+
const sessions = new Sessions(ctx, () => capability.settings(), storage, pairings, restriction, (settings, model) => capability.describe(settings, model));
|
|
15520
16492
|
const capture = async (agent, signal) => {
|
|
16493
|
+
const operation = restriction.begin(agent);
|
|
15521
16494
|
try {
|
|
15522
|
-
const read = sessions.current(agent);
|
|
16495
|
+
const read = sessions.current(liveSession(agent));
|
|
15523
16496
|
const { selection } = signal === void 0 ? await read : await errors.abortable(read, signal);
|
|
15524
16497
|
if (!restriction.active) return {
|
|
15525
16498
|
kind: "failed",
|
|
15526
16499
|
error: contextUnavailable()
|
|
15527
16500
|
};
|
|
15528
|
-
|
|
16501
|
+
let providers = { kind: "unread" };
|
|
16502
|
+
if (restriction.registered && restriction.offered()) try {
|
|
16503
|
+
providers = {
|
|
16504
|
+
kind: "read",
|
|
16505
|
+
ids: await lists.read(agent)
|
|
16506
|
+
};
|
|
16507
|
+
} catch {
|
|
16508
|
+
ctx.logger.warn("%s: conversation provider list could not be read", capability.identity.rowId);
|
|
16509
|
+
}
|
|
16510
|
+
restriction.captured(agent, operation, selection.connectionId, providers);
|
|
15529
16511
|
return planFor(selection);
|
|
15530
16512
|
} catch (error) {
|
|
16513
|
+
restriction.failed(agent, operation);
|
|
15531
16514
|
ctx.logger.warn("%s: capturing %s selection failed: %s", capability.identity.rowId, kind, error);
|
|
15532
16515
|
return {
|
|
15533
16516
|
kind: "failed",
|
|
@@ -15540,13 +16523,33 @@ function installCapability(ctx, capability) {
|
|
|
15540
16523
|
return next();
|
|
15541
16524
|
});
|
|
15542
16525
|
ctx.on("agent/created", async ({ agent }) => {
|
|
16526
|
+
restriction.created(agent);
|
|
16527
|
+
const initial = isNewConversation(agent.session) ? restriction.offered() ? [...capability.settings().connections.values()].filter(isSelectable).map((connection) => connection.id) : [] : void 0;
|
|
16528
|
+
try {
|
|
16529
|
+
await lists.create(agent, initial);
|
|
16530
|
+
} catch {
|
|
16531
|
+
ctx.logger.warn("%s: conversation provider list could not be read", capability.identity.rowId);
|
|
16532
|
+
}
|
|
15543
16533
|
await snapshots.capture(agent, -1, -1, () => capture(agent));
|
|
15544
16534
|
});
|
|
16535
|
+
restriction.start().catch((error) => {
|
|
16536
|
+
ctx.logger.warn("%s: starting tool visibility failed: %s", capability.identity.rowId, error);
|
|
16537
|
+
});
|
|
15545
16538
|
ctx.on("agent/disposed", ({ agent }) => {
|
|
15546
16539
|
snapshots.forget(agent);
|
|
15547
16540
|
restriction.forget(agent);
|
|
15548
16541
|
});
|
|
15549
|
-
ctx.inject(["
|
|
16542
|
+
ctx.inject(["commands"], (scope) => {
|
|
16543
|
+
registerCommand(scope, capability, sessions);
|
|
16544
|
+
});
|
|
16545
|
+
ctx.inject(["tuiCommandTrees"], (scope) => {
|
|
16546
|
+
registerTerminal(scope, capability.identity.command, "web " + kind, () => capability.settings(), capability.commandOptions !== void 0);
|
|
16547
|
+
});
|
|
16548
|
+
ctx.inject([
|
|
16549
|
+
"sessionController",
|
|
16550
|
+
"typert",
|
|
16551
|
+
"agents"
|
|
16552
|
+
], (scope) => {
|
|
15550
16553
|
createConnectionsService(scope, {
|
|
15551
16554
|
namespace: capability.remoteNamespace,
|
|
15552
16555
|
contract: capability.contract,
|
|
@@ -16428,7 +17431,7 @@ const { withDeadline, readJson, requestJson, invalidResponse } = transport;
|
|
|
16428
17431
|
const mcp = createMcpClient(transport, searchErrors, {
|
|
16429
17432
|
clientInfo: {
|
|
16430
17433
|
name: SEARCH.packageName,
|
|
16431
|
-
version: "0.2.
|
|
17434
|
+
version: "0.2.2"
|
|
16432
17435
|
},
|
|
16433
17436
|
messages: {
|
|
16434
17437
|
toolFailed: (label, detail) => label + " could not complete the search; retry later or choose another search connection" + upstreamSuffix(label, detail),
|
|
@@ -16534,6 +17537,7 @@ const location = {
|
|
|
16534
17537
|
const MODEL_PROTOCOLS = {
|
|
16535
17538
|
"anthropic-messages": {
|
|
16536
17539
|
label: "Anthropic Messages",
|
|
17540
|
+
use: "the model searches and answers",
|
|
16537
17541
|
options: {
|
|
16538
17542
|
maxTokens,
|
|
16539
17543
|
anthropicTool,
|
|
@@ -16549,6 +17553,7 @@ const MODEL_PROTOCOLS = {
|
|
|
16549
17553
|
},
|
|
16550
17554
|
"openai-responses": {
|
|
16551
17555
|
label: "OpenAI Responses",
|
|
17556
|
+
use: "the model searches and answers",
|
|
16552
17557
|
options: {
|
|
16553
17558
|
maxTokens,
|
|
16554
17559
|
responsesTool,
|
|
@@ -16562,6 +17567,7 @@ const MODEL_PROTOCOLS = {
|
|
|
16562
17567
|
},
|
|
16563
17568
|
"openai-chat-completions": {
|
|
16564
17569
|
label: "OpenAI Chat Completions",
|
|
17570
|
+
use: "the model searches and answers",
|
|
16565
17571
|
options: {
|
|
16566
17572
|
maxTokens,
|
|
16567
17573
|
searchContextSize,
|
|
@@ -16593,6 +17599,8 @@ const CATALOG = {
|
|
|
16593
17599
|
label: "DeepSeek Messages",
|
|
16594
17600
|
kind: "native",
|
|
16595
17601
|
remote: true,
|
|
17602
|
+
optIn: false,
|
|
17603
|
+
use: "answers with a summary and sources",
|
|
16596
17604
|
endpoint: "https://api.deepseek.com/anthropic/v1",
|
|
16597
17605
|
homepage: "https://platform.deepseek.com",
|
|
16598
17606
|
access: nativeAccess,
|
|
@@ -16623,6 +17631,7 @@ const CATALOG = {
|
|
|
16623
17631
|
label: "DeepSeek Search API",
|
|
16624
17632
|
kind: "native",
|
|
16625
17633
|
remote: true,
|
|
17634
|
+
optIn: true,
|
|
16626
17635
|
endpoint: "https://api.deepseek.com/v1/tools",
|
|
16627
17636
|
homepage: "https://platform.deepseek.com",
|
|
16628
17637
|
access: nativeAccess,
|
|
@@ -16641,6 +17650,8 @@ const CATALOG = {
|
|
|
16641
17650
|
label: "Session model",
|
|
16642
17651
|
kind: "model",
|
|
16643
17652
|
remote: true,
|
|
17653
|
+
optIn: false,
|
|
17654
|
+
use: "the session model searches and answers",
|
|
16644
17655
|
access: {
|
|
16645
17656
|
keyless: false,
|
|
16646
17657
|
apiKey: false,
|
|
@@ -16660,6 +17671,7 @@ const CATALOG = {
|
|
|
16660
17671
|
},
|
|
16661
17672
|
kind: "adapter",
|
|
16662
17673
|
remote: true,
|
|
17674
|
+
optIn: true,
|
|
16663
17675
|
freshness: ["api-key"],
|
|
16664
17676
|
homepage: "https://exa.ai",
|
|
16665
17677
|
options: { type: {
|
|
@@ -16682,6 +17694,7 @@ const CATALOG = {
|
|
|
16682
17694
|
},
|
|
16683
17695
|
kind: "adapter",
|
|
16684
17696
|
remote: true,
|
|
17697
|
+
optIn: true,
|
|
16685
17698
|
freshness: ["keyless", "api-key"],
|
|
16686
17699
|
homepage: "https://www.firecrawl.dev",
|
|
16687
17700
|
options: {
|
|
@@ -16707,6 +17720,7 @@ const CATALOG = {
|
|
|
16707
17720
|
},
|
|
16708
17721
|
kind: "adapter",
|
|
16709
17722
|
remote: true,
|
|
17723
|
+
optIn: true,
|
|
16710
17724
|
freshness: [],
|
|
16711
17725
|
homepage: "https://www.tavily.com",
|
|
16712
17726
|
options: {
|
|
@@ -16739,6 +17753,7 @@ const CATALOG = {
|
|
|
16739
17753
|
},
|
|
16740
17754
|
kind: "adapter",
|
|
16741
17755
|
remote: true,
|
|
17756
|
+
optIn: true,
|
|
16742
17757
|
freshness: [],
|
|
16743
17758
|
homepage: "https://www.tinyfish.ai",
|
|
16744
17759
|
options: {
|
|
@@ -16775,8 +17790,10 @@ const CATALOG = {
|
|
|
16775
17790
|
},
|
|
16776
17791
|
kind: "adapter",
|
|
16777
17792
|
remote: true,
|
|
17793
|
+
optIn: true,
|
|
16778
17794
|
freshness: [],
|
|
16779
17795
|
homepage: "https://openalex.org",
|
|
17796
|
+
use: "scholarly works (papers, citations); keyword queries",
|
|
16780
17797
|
options: {
|
|
16781
17798
|
sort: {
|
|
16782
17799
|
kind: "text",
|
|
@@ -16805,8 +17822,10 @@ const CATALOG = {
|
|
|
16805
17822
|
},
|
|
16806
17823
|
kind: "adapter",
|
|
16807
17824
|
remote: true,
|
|
17825
|
+
optIn: true,
|
|
16808
17826
|
freshness: [],
|
|
16809
17827
|
homepage: "https://www.semanticscholar.org/product/api",
|
|
17828
|
+
use: "academic papers (citations, TL;DR); keyword queries",
|
|
16810
17829
|
options: {
|
|
16811
17830
|
year: {
|
|
16812
17831
|
kind: "text",
|
|
@@ -17066,17 +18085,9 @@ function sourceFields(source) {
|
|
|
17066
18085
|
}
|
|
17067
18086
|
//#endregion
|
|
17068
18087
|
//#region src/model/execute.ts
|
|
17069
|
-
const UNSUPPORTED = {
|
|
17070
|
-
"no-model": "no session model is available",
|
|
17071
|
-
"unknown-route": "the model route is unknown",
|
|
17072
|
-
protocol: "the route does not declare a supported protocol",
|
|
17073
|
-
endpoint: "the route has no supported endpoint",
|
|
17074
|
-
headers: "the route uses custom request headers",
|
|
17075
|
-
credential: "the route has no supported credential reference"
|
|
17076
|
-
};
|
|
17077
18088
|
async function executeModelSearch(target, request, deps, signal) {
|
|
17078
18089
|
throwIfAborted(signal);
|
|
17079
|
-
if (target.route.kind === "unsupported") throw searchError("WEB_PROVIDER_CONFIGURED_UNAVAILABLE", "the session model cannot run web search: " +
|
|
18090
|
+
if (target.route.kind === "unsupported") throw searchError("WEB_PROVIDER_CONFIGURED_UNAVAILABLE", "the session model cannot run web search: " + UNSUPPORTED_REASON_TEXT[target.route.reason] + "; choose another search connection");
|
|
17080
18091
|
const route = target.route.route;
|
|
17081
18092
|
let matcher;
|
|
17082
18093
|
try {
|
|
@@ -17114,15 +18125,24 @@ async function executeModelSearch(target, request, deps, signal) {
|
|
|
17114
18125
|
async function credential(route, deps, signal) {
|
|
17115
18126
|
const { auth } = route;
|
|
17116
18127
|
if (auth.kind === "account") {
|
|
17117
|
-
const
|
|
18128
|
+
const endpoint = modelUrl(route.protocol, route.baseURL);
|
|
18129
|
+
const token = await readCredential(() => deps.accountToken(endpoint), signal);
|
|
17118
18130
|
if (token === void 0 || !/^[\x20-\x7e]+$/u.test(token)) throw searchError("WEB_PROVIDER_CREDENTIAL_MISSING", "the DeepSeek account sign-in is unavailable for this search; ask the user to sign in again or choose another search connection");
|
|
17119
18131
|
return token;
|
|
17120
18132
|
}
|
|
17121
|
-
const stored = storedApiKey(await
|
|
18133
|
+
const stored = storedApiKey(await readCredential(() => deps.apiKey(auth.credentialRef, auth.source), signal));
|
|
17122
18134
|
if (stored.kind === "missing") throw searchError("WEB_PROVIDER_CREDENTIAL_MISSING", credentialMissing(auth.credentialRef, auth.source));
|
|
17123
18135
|
if (stored.kind === "malformed") throw searchError("WEB_PROVIDER_CREDENTIAL_MISSING", credentialInvalid(auth.credentialRef, auth.source));
|
|
17124
18136
|
return stored.key;
|
|
17125
18137
|
}
|
|
18138
|
+
async function readCredential(resolve, signal) {
|
|
18139
|
+
try {
|
|
18140
|
+
return await abortable(resolve(), signal);
|
|
18141
|
+
} catch {
|
|
18142
|
+
throwIfAborted(signal);
|
|
18143
|
+
throw new Error("Model credential resolution failed");
|
|
18144
|
+
}
|
|
18145
|
+
}
|
|
17126
18146
|
//#endregion
|
|
17127
18147
|
//#region src/contract.ts
|
|
17128
18148
|
const contract = selectionContract({
|
|
@@ -17216,15 +18236,287 @@ function createSearchSeat(id, select) {
|
|
|
17216
18236
|
};
|
|
17217
18237
|
}
|
|
17218
18238
|
//#endregion
|
|
18239
|
+
//#region src/host/listing.ts
|
|
18240
|
+
/**
|
|
18241
|
+
* What list_web_search_providers says about one connection (docs/design.md, "列出工具"):
|
|
18242
|
+
* its catalog use, who receives the query and whether it applies the Session's
|
|
18243
|
+
* freshness. Reads only settings and public model facts; no I/O, no secrets.
|
|
18244
|
+
*/
|
|
18245
|
+
function searchListing(ctx, connection, call) {
|
|
18246
|
+
switch (connection.kind) {
|
|
18247
|
+
case "native": {
|
|
18248
|
+
const info = CATALOG[connection.native];
|
|
18249
|
+
const { baseURL } = connection.options;
|
|
18250
|
+
return {
|
|
18251
|
+
use: info.use,
|
|
18252
|
+
recipient: service(info.label, typeof baseURL === "string" ? baseURL : info.endpoint, info.endpoint),
|
|
18253
|
+
appliesFreshness: false
|
|
18254
|
+
};
|
|
18255
|
+
}
|
|
18256
|
+
case "adapter": {
|
|
18257
|
+
const info = CATALOG[connection.adapter];
|
|
18258
|
+
const endpoint = connection.access === "keyless" ? info.endpoint : connection.endpoint;
|
|
18259
|
+
return {
|
|
18260
|
+
use: info.use,
|
|
18261
|
+
recipient: service(info.label, endpoint, info.endpoint),
|
|
18262
|
+
appliesFreshness: honorsFreshness(connection.adapter, connection.access)
|
|
18263
|
+
};
|
|
18264
|
+
}
|
|
18265
|
+
case "model": {
|
|
18266
|
+
if (connection.route.kind === "session") {
|
|
18267
|
+
const model = nextModel(ctx, call.agent);
|
|
18268
|
+
return {
|
|
18269
|
+
use: CATALOG["session-model"].use,
|
|
18270
|
+
recipient: {
|
|
18271
|
+
kind: "session-model",
|
|
18272
|
+
provider: model === void 0 ? void 0 : model.provider
|
|
18273
|
+
},
|
|
18274
|
+
appliesFreshness: false
|
|
18275
|
+
};
|
|
18276
|
+
}
|
|
18277
|
+
const protocol = MODEL_PROTOCOLS[connection.route.protocol];
|
|
18278
|
+
return {
|
|
18279
|
+
use: protocol.use,
|
|
18280
|
+
recipient: {
|
|
18281
|
+
kind: "service",
|
|
18282
|
+
service: protocol.label,
|
|
18283
|
+
host: new URL(connection.route.endpoint).hostname
|
|
18284
|
+
},
|
|
18285
|
+
appliesFreshness: false
|
|
18286
|
+
};
|
|
18287
|
+
}
|
|
18288
|
+
}
|
|
18289
|
+
}
|
|
18290
|
+
/** The catalog service; an operator endpoint that differs from the catalog's is named by its hostname. */
|
|
18291
|
+
function service(label, endpoint, catalogEndpoint) {
|
|
18292
|
+
return {
|
|
18293
|
+
kind: "service",
|
|
18294
|
+
service: label,
|
|
18295
|
+
host: endpoint === catalogEndpoint ? void 0 : new URL(endpoint).hostname
|
|
18296
|
+
};
|
|
18297
|
+
}
|
|
18298
|
+
//#endregion
|
|
18299
|
+
//#region src/tools/queries.ts
|
|
18300
|
+
/**
|
|
18301
|
+
* The multi-query part of `web_search`, copied from `@deepseek-ai/dsh-tool-web`
|
|
18302
|
+
* 0.2.1-rc.2 (packages/web/tool-web/src/search.ts: `parseSearchArgs`,
|
|
18303
|
+
* `runSearchQueries`, `mergeSearchResults`, `projectSource`) and
|
|
18304
|
+
* `@deepseek-ai/dsh-web` (packages/web/web/src/index.ts: `capSources`), which do
|
|
18305
|
+
* not export them. The enhanced tool bypasses `ctx.web`, so it applies the
|
|
18306
|
+
* seam's `capSources` itself. Keep in step with DSH on baseline upgrades;
|
|
18307
|
+
* tests/tools-search.spec.ts compares the result with the real tool-web.
|
|
18308
|
+
*/
|
|
18309
|
+
/** tool-web's `parseSearchArgs`: 1–4 non-blank queries, exact duplicates collapsed after the bound check. */
|
|
18310
|
+
function parseQueries(queries) {
|
|
18311
|
+
if (queries.length === 0) throw invalidArguments("queries must contain at least one query");
|
|
18312
|
+
if (queries.length > WEB_SEARCH_MAX_QUERIES) throw invalidArguments(`queries must contain at most ${WEB_SEARCH_MAX_QUERIES} queries`);
|
|
18313
|
+
if (queries.some((query) => query.trim().length === 0)) throw invalidArguments("each query must be a non-empty string");
|
|
18314
|
+
return [...new Set(queries)];
|
|
18315
|
+
}
|
|
18316
|
+
/**
|
|
18317
|
+
* tool-web's `runSearchQueries`: one query keeps its (capped) result; several
|
|
18318
|
+
* run concurrently and merge. A failed search aborts its siblings, and the
|
|
18319
|
+
* first failure is rethrown once every search has settled.
|
|
18320
|
+
*/
|
|
18321
|
+
async function searchQueries(queries, run, signal) {
|
|
18322
|
+
const search = async (query, querySignal) => capSources(await run({
|
|
18323
|
+
query,
|
|
18324
|
+
maxResults: WEB_SEARCH_MAX_RESULTS
|
|
18325
|
+
}, querySignal), WEB_SEARCH_MAX_RESULTS);
|
|
18326
|
+
const [only] = queries;
|
|
18327
|
+
if (queries.length === 1 && only !== void 0) return search(only, signal);
|
|
18328
|
+
const controller = new AbortController();
|
|
18329
|
+
const batchSignal = AbortSignal.any([signal, controller.signal]);
|
|
18330
|
+
let firstFailure;
|
|
18331
|
+
const results = [];
|
|
18332
|
+
const searches = queries.map(async (query, index) => {
|
|
18333
|
+
try {
|
|
18334
|
+
results[index] = await search(query, batchSignal);
|
|
18335
|
+
} catch (error) {
|
|
18336
|
+
if (firstFailure === void 0) firstFailure = { error };
|
|
18337
|
+
controller.abort(error);
|
|
18338
|
+
throw error;
|
|
18339
|
+
}
|
|
18340
|
+
});
|
|
18341
|
+
await Promise.allSettled(searches);
|
|
18342
|
+
if (firstFailure !== void 0) throw firstFailure.error;
|
|
18343
|
+
return mergeSearchResults(queries, results, WEB_SEARCH_MAX_RESULTS);
|
|
18344
|
+
}
|
|
18345
|
+
/** tool-web's `mergeSearchResults`: round-robin by rank, deduplicated by URL, capped; answers joined under `### <query>`. */
|
|
18346
|
+
function mergeSearchResults(queries, results, maxResults) {
|
|
18347
|
+
const seen = /* @__PURE__ */ new Set();
|
|
18348
|
+
const sources = [];
|
|
18349
|
+
let sourceRanks = 0;
|
|
18350
|
+
for (const result of results) sourceRanks = Math.max(sourceRanks, result.sources.length);
|
|
18351
|
+
let droppedSource = false;
|
|
18352
|
+
merge: for (let rank = 0; rank < sourceRanks; rank++) for (const result of results) {
|
|
18353
|
+
const source = result.sources[rank];
|
|
18354
|
+
if (source !== void 0 && !seen.has(source.url)) {
|
|
18355
|
+
seen.add(source.url);
|
|
18356
|
+
if (sources.length === maxResults) {
|
|
18357
|
+
droppedSource = true;
|
|
18358
|
+
break merge;
|
|
18359
|
+
}
|
|
18360
|
+
sources.push(source);
|
|
18361
|
+
}
|
|
18362
|
+
}
|
|
18363
|
+
const contents = results.flatMap((result, index) => {
|
|
18364
|
+
if (result.content === void 0 || result.content.length === 0) return [];
|
|
18365
|
+
return [`### ${queries[index]}\n\n${result.content}`];
|
|
18366
|
+
});
|
|
18367
|
+
return {
|
|
18368
|
+
...contents.length > 0 ? { content: contents.join("\n\n") } : {},
|
|
18369
|
+
sources,
|
|
18370
|
+
truncated: results.some((result) => result.truncated) || droppedSource
|
|
18371
|
+
};
|
|
18372
|
+
}
|
|
18373
|
+
/** dsh-web's `capSources`: the seam's cut of an over-returning provider. */
|
|
18374
|
+
function capSources(result, maxResults) {
|
|
18375
|
+
if (result.sources.length <= maxResults) return result;
|
|
18376
|
+
return {
|
|
18377
|
+
...result,
|
|
18378
|
+
sources: result.sources.slice(0, maxResults),
|
|
18379
|
+
truncated: true
|
|
18380
|
+
};
|
|
18381
|
+
}
|
|
18382
|
+
/** tool-web's `projectSource`: `{ url }` plus each present optional field. */
|
|
18383
|
+
function projectSource(source) {
|
|
18384
|
+
return {
|
|
18385
|
+
url: source.url,
|
|
18386
|
+
...source.title !== void 0 ? { title: source.title } : {},
|
|
18387
|
+
...source.snippet !== void 0 ? { snippet: source.snippet } : {},
|
|
18388
|
+
...source.publishedAt !== void 0 ? { publishedAt: source.publishedAt } : {}
|
|
18389
|
+
};
|
|
18390
|
+
}
|
|
18391
|
+
//#endregion
|
|
18392
|
+
//#region src/tools/definitions.ts
|
|
18393
|
+
const { action, list } = SEARCH.tools;
|
|
18394
|
+
const VOICE = {
|
|
18395
|
+
identity: SEARCH,
|
|
18396
|
+
capability: "web search",
|
|
18397
|
+
nativeTool: "web_search",
|
|
18398
|
+
sends: "queries"
|
|
18399
|
+
};
|
|
18400
|
+
/** Slightly longer than the remote backends' 90 s deadline, so their worded timeout arrives first (docs/design.md, "工具与输出"). */
|
|
18401
|
+
const SEARCH_TIMEOUT_MS = 1e5;
|
|
18402
|
+
/** `web_search`'s output schema plus the provider that served the call. */
|
|
18403
|
+
const SEARCH_RESULT_SCHEMA = {
|
|
18404
|
+
type: "object",
|
|
18405
|
+
additionalProperties: false,
|
|
18406
|
+
properties: {
|
|
18407
|
+
provider: {
|
|
18408
|
+
type: "string",
|
|
18409
|
+
required: true
|
|
18410
|
+
},
|
|
18411
|
+
content: { type: "string" },
|
|
18412
|
+
sources: {
|
|
18413
|
+
type: "array",
|
|
18414
|
+
required: true,
|
|
18415
|
+
items: {
|
|
18416
|
+
type: "object",
|
|
18417
|
+
additionalProperties: false,
|
|
18418
|
+
properties: {
|
|
18419
|
+
url: {
|
|
18420
|
+
type: "string",
|
|
18421
|
+
required: true
|
|
18422
|
+
},
|
|
18423
|
+
title: { type: "string" },
|
|
18424
|
+
snippet: { type: "string" },
|
|
18425
|
+
publishedAt: { type: "string" }
|
|
18426
|
+
}
|
|
18427
|
+
}
|
|
18428
|
+
},
|
|
18429
|
+
truncated: {
|
|
18430
|
+
type: "boolean",
|
|
18431
|
+
required: true
|
|
18432
|
+
}
|
|
18433
|
+
}
|
|
18434
|
+
};
|
|
18435
|
+
/** The two static search definitions (docs/design.md, "工具与输出"); available providers never enter them. */
|
|
18436
|
+
function searchTools(access) {
|
|
18437
|
+
return {
|
|
18438
|
+
action: defineTool({
|
|
18439
|
+
name: action,
|
|
18440
|
+
description: `Search the web through a provider you choose. Use web_search unless you need a specific provider: one the user named, a specialized index, or another after web_search fails.`,
|
|
18441
|
+
parameters: {
|
|
18442
|
+
queries: {
|
|
18443
|
+
type: "array",
|
|
18444
|
+
required: true,
|
|
18445
|
+
items: { type: "string" },
|
|
18446
|
+
description: `1–${WEB_SEARCH_MAX_QUERIES} search queries; their results are merged.`
|
|
18447
|
+
},
|
|
18448
|
+
provider: {
|
|
18449
|
+
type: "string",
|
|
18450
|
+
required: true,
|
|
18451
|
+
description: `Provider id exactly as listed by ${list}.`
|
|
18452
|
+
}
|
|
18453
|
+
},
|
|
18454
|
+
output: {
|
|
18455
|
+
schema: SEARCH_RESULT_SCHEMA,
|
|
18456
|
+
render: (_args, value) => [{
|
|
18457
|
+
type: "text",
|
|
18458
|
+
text: `Provider: ${value.provider}\n\n${formatSearchOutput(value)}`
|
|
18459
|
+
}],
|
|
18460
|
+
presentationMeta: (_args, value) => searchMetaFromValue(value)
|
|
18461
|
+
},
|
|
18462
|
+
timeoutMs: SEARCH_TIMEOUT_MS,
|
|
18463
|
+
isConcurrencySafe: () => true,
|
|
18464
|
+
async execute(args, exec) {
|
|
18465
|
+
rejectUnknownProperties(args, ["queries", "provider"]);
|
|
18466
|
+
const queries = parseQueries(args.queries);
|
|
18467
|
+
const call = explicitCall(action, exec);
|
|
18468
|
+
const provider = await providerArgument(VOICE, args.provider, access, call);
|
|
18469
|
+
const result = await searchQueries(queries, (request, signal) => runOnProvider(VOICE, access, provider, request, {
|
|
18470
|
+
agent: call.agent,
|
|
18471
|
+
signal
|
|
18472
|
+
}), call.signal);
|
|
18473
|
+
return {
|
|
18474
|
+
provider,
|
|
18475
|
+
...result.content !== void 0 ? { content: result.content } : {},
|
|
18476
|
+
sources: result.sources.map(projectSource),
|
|
18477
|
+
truncated: result.truncated
|
|
18478
|
+
};
|
|
18479
|
+
}
|
|
18480
|
+
}),
|
|
18481
|
+
list: defineTool({
|
|
18482
|
+
name: list,
|
|
18483
|
+
description: `List the providers ${action} accepts, with what each is for and whether it is ready.`,
|
|
18484
|
+
parameters: {},
|
|
18485
|
+
output: {
|
|
18486
|
+
schema: searchListingSchema(),
|
|
18487
|
+
render: (_args, value) => [{
|
|
18488
|
+
type: "text",
|
|
18489
|
+
text: renderSearchListing(VOICE, value)
|
|
18490
|
+
}]
|
|
18491
|
+
},
|
|
18492
|
+
isConcurrencySafe: () => true,
|
|
18493
|
+
async execute(args, exec) {
|
|
18494
|
+
rejectUnknownProperties(args, []);
|
|
18495
|
+
return searchListingValue(await listProviders(VOICE, access, explicitCall(list, exec)));
|
|
18496
|
+
}
|
|
18497
|
+
})
|
|
18498
|
+
};
|
|
18499
|
+
}
|
|
18500
|
+
//#endregion
|
|
18501
|
+
//#region src/tools/index.ts
|
|
18502
|
+
/** The search side of the enhanced tools (docs/design.md, "增强工具"). */
|
|
18503
|
+
function searchEnhanced(ctx) {
|
|
18504
|
+
return {
|
|
18505
|
+
names: SEARCH.tools,
|
|
18506
|
+
tools: searchTools,
|
|
18507
|
+
listing: (connection, call) => searchListing(ctx, connection, call)
|
|
18508
|
+
};
|
|
18509
|
+
}
|
|
18510
|
+
//#endregion
|
|
17219
18511
|
//#region src/host/connections.ts
|
|
17220
|
-
async function describeConnections(ctx, settings,
|
|
18512
|
+
async function describeConnections(ctx, settings, model) {
|
|
17221
18513
|
return Promise.all([...settings.connections.values()].map(async (connection) => {
|
|
17222
18514
|
const base = baseOf(connection);
|
|
17223
18515
|
if (connection.kind === "model") {
|
|
17224
18516
|
const target = connection.route.kind === "fixed" ? {
|
|
17225
18517
|
kind: "route",
|
|
17226
18518
|
route: fixedRoute(connection.route)
|
|
17227
|
-
} : sessionRoute(ctx,
|
|
18519
|
+
} : sessionRoute(ctx, model);
|
|
17228
18520
|
const access = target.kind === "route" ? target.route.auth.kind : "api-key";
|
|
17229
18521
|
if (connection.disabled) return {
|
|
17230
18522
|
...base,
|
|
@@ -17257,7 +18549,7 @@ async function describeConnections(ctx, settings, agent) {
|
|
|
17257
18549
|
const endpoint = connection.kind === "native" ? typeof connection.options.baseURL === "string" ? connection.options.baseURL : CATALOG[connection.native].endpoint : void 0;
|
|
17258
18550
|
return {
|
|
17259
18551
|
...base,
|
|
17260
|
-
...await credentialReadiness(ctx,
|
|
18552
|
+
...await credentialReadiness(ctx, model, {
|
|
17261
18553
|
endpoint,
|
|
17262
18554
|
credentialRef: connection.credentialRef
|
|
17263
18555
|
})
|
|
@@ -17302,7 +18594,11 @@ function searchCapability(ctx, settings) {
|
|
|
17302
18594
|
contract,
|
|
17303
18595
|
optionsCodec,
|
|
17304
18596
|
settings,
|
|
17305
|
-
|
|
18597
|
+
commandOptions: {
|
|
18598
|
+
freshness: (options) => options.freshness,
|
|
18599
|
+
changeFreshness: (freshness) => ({ freshness })
|
|
18600
|
+
},
|
|
18601
|
+
describe: (current, model) => describeConnections(ctx, current, model),
|
|
17306
18602
|
target: (connection, options, call) => {
|
|
17307
18603
|
switch (connection.kind) {
|
|
17308
18604
|
case "adapter": return {
|
|
@@ -17349,7 +18645,8 @@ function searchCapability(ctx, settings) {
|
|
|
17349
18645
|
available: () => true,
|
|
17350
18646
|
search: invoke
|
|
17351
18647
|
})));
|
|
17352
|
-
}
|
|
18648
|
+
},
|
|
18649
|
+
enhanced: searchEnhanced(ctx)
|
|
17353
18650
|
};
|
|
17354
18651
|
}
|
|
17355
18652
|
function installSearch(ctx, settings) {
|