@axiom-lattice/gateway 6.0.1 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/embed/{embed-page-AgentDetailSideApp-JYVZKSUU.js → embed-page-AgentDetailSideApp-2UFH4RJG.js} +1 -1
- package/dist/embed/{embed-page-AgentGovernanceWorkbench-SD2EHW2B.js → embed-page-AgentGovernanceWorkbench-3JOSRW2V.js} +1 -1
- package/dist/embed/{embed-page-MessageGraph-GLQXKAVC.js → embed-page-MessageGraph-SPB75EHC.js} +1 -1
- package/dist/embed/{embed-page-ReactChart-74MHCE2K.js → embed-page-ReactChart-EE4A5WKS.js} +1 -1
- package/dist/embed/{embed-page-SkillFlow-NDAK3GAD.js → embed-page-SkillFlow-H2ARZGEX.js} +1 -1
- package/dist/embed/{embed-page-TasksSideApp-DA4LUTMD.js → embed-page-TasksSideApp-BQMOLOUF.js} +1 -1
- package/dist/embed/{embed-page-TeamWorkspace-DUB3L6V6.js → embed-page-TeamWorkspace-QXMRVXWU.js} +1 -1
- package/dist/embed/{embed-page-TopologyRuntimeView-NFBHAICN.js → embed-page-TopologyRuntimeView-OSV75P6Y.js} +1 -1
- package/dist/embed/{embed-page-WorkflowAutomationView-43YGTXGF.js → embed-page-WorkflowAutomationView-KJ6HJ5UT.js} +1 -1
- package/dist/embed/{embed-page-chunk-UHH575CK.js → embed-page-chunk-DBUC6J4G.js} +1 -1
- package/dist/embed/{embed-page-chunk-C4ZV4IXK.js → embed-page-chunk-E6F5YZBR.js} +1 -1
- package/dist/embed/{embed-page-chunk-BVSC3MSO.js → embed-page-chunk-FGKHXQAJ.js} +1 -1
- package/dist/embed/{embed-page-chunk-2PFKLMTR.js → embed-page-chunk-GSCVATOA.js} +1 -1
- package/dist/embed/{embed-page-chunk-GKHIPXZ2.js → embed-page-chunk-IIEDLN7W.js} +1 -1
- package/dist/embed/{embed-page-chunk-P477ZTUO.js → embed-page-chunk-U4XSGC4Y.js} +163 -163
- package/dist/embed/{embed-page-chunk-DWIBLGBW.js → embed-page-chunk-VXWRPT33.js} +1 -1
- package/dist/embed/{embed-page-en-S4MXFPIU.js → embed-page-en-X6B6RVNL.js} +1 -1
- package/dist/embed/{embed-page-zh-SSMSF5LE.js → embed-page-zh-EQ5X2M32.js} +1 -1
- package/dist/embed/embed-page.js +1 -1
- package/dist/index.d.mts +43 -8
- package/dist/index.d.ts +43 -8
- package/dist/index.js +1117 -365
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +907 -148
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -7
package/dist/index.mjs
CHANGED
|
@@ -2678,13 +2678,13 @@ async function searchEntries(request, reply) {
|
|
|
2678
2678
|
|
|
2679
2679
|
// src/controllers/tools.ts
|
|
2680
2680
|
import { toolLatticeManager } from "@axiom-lattice/core";
|
|
2681
|
-
function serializeSchema(
|
|
2682
|
-
if (!
|
|
2681
|
+
function serializeSchema(schema2) {
|
|
2682
|
+
if (!schema2) {
|
|
2683
2683
|
return void 0;
|
|
2684
2684
|
}
|
|
2685
2685
|
try {
|
|
2686
|
-
if (
|
|
2687
|
-
const def =
|
|
2686
|
+
if (schema2._def) {
|
|
2687
|
+
const def = schema2._def;
|
|
2688
2688
|
if (def.typeName === "ZodObject") {
|
|
2689
2689
|
const shape = def.shape();
|
|
2690
2690
|
const properties = {};
|
|
@@ -2710,7 +2710,7 @@ function serializeSchema(schema) {
|
|
|
2710
2710
|
}
|
|
2711
2711
|
return {
|
|
2712
2712
|
type: "object",
|
|
2713
|
-
description:
|
|
2713
|
+
description: schema2.description || "Schema definition"
|
|
2714
2714
|
};
|
|
2715
2715
|
} catch (error) {
|
|
2716
2716
|
return {
|
|
@@ -11401,7 +11401,50 @@ function registerA2ABridgeRoutes(app2) {
|
|
|
11401
11401
|
}
|
|
11402
11402
|
|
|
11403
11403
|
// src/routes/stt.ts
|
|
11404
|
-
import {
|
|
11404
|
+
import { sttModelLatticeManager } from "@axiom-lattice/core";
|
|
11405
|
+
|
|
11406
|
+
// src/controllers/tenant-scope.ts
|
|
11407
|
+
var TenantScopeError2 = class extends Error {
|
|
11408
|
+
/** Creates a tenant scope error. */
|
|
11409
|
+
constructor(code, statusCode, message) {
|
|
11410
|
+
super(message);
|
|
11411
|
+
this.code = code;
|
|
11412
|
+
this.statusCode = statusCode;
|
|
11413
|
+
this.name = "TenantScopeError";
|
|
11414
|
+
}
|
|
11415
|
+
};
|
|
11416
|
+
function resolveTenantScope(request, authRequired2) {
|
|
11417
|
+
const authenticated = request.user?.tenantId;
|
|
11418
|
+
const header2 = request.headers["x-tenant-id"];
|
|
11419
|
+
const requested = Array.isArray(header2) ? header2[0] : header2;
|
|
11420
|
+
if (authenticated) {
|
|
11421
|
+
if (requested && requested !== authenticated) {
|
|
11422
|
+
throw new TenantScopeError2(
|
|
11423
|
+
"TENANT_ACCESS_DENIED",
|
|
11424
|
+
403,
|
|
11425
|
+
"Tenant access denied"
|
|
11426
|
+
);
|
|
11427
|
+
}
|
|
11428
|
+
return { tenantId: authenticated, userId: request.user?.id };
|
|
11429
|
+
}
|
|
11430
|
+
if (authRequired2) {
|
|
11431
|
+
throw new TenantScopeError2(
|
|
11432
|
+
"UNAUTHENTICATED",
|
|
11433
|
+
401,
|
|
11434
|
+
"Console session required"
|
|
11435
|
+
);
|
|
11436
|
+
}
|
|
11437
|
+
return { tenantId: requested || "default" };
|
|
11438
|
+
}
|
|
11439
|
+
|
|
11440
|
+
// src/routes/stt.ts
|
|
11441
|
+
function tenantIdOf(request) {
|
|
11442
|
+
try {
|
|
11443
|
+
return resolveTenantScope(request, false).tenantId;
|
|
11444
|
+
} catch {
|
|
11445
|
+
return "default";
|
|
11446
|
+
}
|
|
11447
|
+
}
|
|
11405
11448
|
var ALLOWED_AUDIO_FORMATS = /* @__PURE__ */ new Set([
|
|
11406
11449
|
"webm",
|
|
11407
11450
|
"wav",
|
|
@@ -11455,10 +11498,11 @@ var registerSTTRoutes = (app2) => {
|
|
|
11455
11498
|
return reply.status(400).send({ error: `Unsupported audio format: ${format}` });
|
|
11456
11499
|
}
|
|
11457
11500
|
try {
|
|
11458
|
-
|
|
11459
|
-
|
|
11460
|
-
|
|
11461
|
-
|
|
11501
|
+
const client = await sttModelLatticeManager.getSTTClientWithTenantAsync(
|
|
11502
|
+
tenantIdOf(request),
|
|
11503
|
+
modelKey2
|
|
11504
|
+
);
|
|
11505
|
+
if (!client) {
|
|
11462
11506
|
return reply.status(404).send({ error: `STT model "${modelKey2}" not found` });
|
|
11463
11507
|
}
|
|
11464
11508
|
const result = await client.transcribe(buffer, format);
|
|
@@ -11752,6 +11796,9 @@ function registerTaskRoutes(app2, controller) {
|
|
|
11752
11796
|
import {
|
|
11753
11797
|
agentInstanceManager as agentInstanceManager6,
|
|
11754
11798
|
agentLatticeManager as agentLatticeManager4,
|
|
11799
|
+
embeddingsLatticeManager as embeddingsLatticeManager2,
|
|
11800
|
+
sttModelLatticeManager as sttModelLatticeManager2,
|
|
11801
|
+
vectorStoreLatticeManager,
|
|
11755
11802
|
createProjectTaskReassignmentAuthorizer,
|
|
11756
11803
|
createTaskLifecycleService,
|
|
11757
11804
|
createTrustedAgentMessageDispatcher,
|
|
@@ -15721,6 +15768,7 @@ import { toModelKey } from "@axiom-lattice/protocols";
|
|
|
15721
15768
|
import {
|
|
15722
15769
|
assertSafeUpstreamUrl,
|
|
15723
15770
|
buildModelLlmConfig,
|
|
15771
|
+
DEFAULT_MODEL_EXTRA,
|
|
15724
15772
|
UnsupportedModelProviderError,
|
|
15725
15773
|
UnsafeUpstreamUrlError
|
|
15726
15774
|
} from "@axiom-lattice/core";
|
|
@@ -15763,13 +15811,48 @@ function resolveDiscoveryTimeoutMs(raw = process.env.LATTICE_MODEL_DISCOVERY_TIM
|
|
|
15763
15811
|
return parsed;
|
|
15764
15812
|
}
|
|
15765
15813
|
function buildModelsEndpoint(baseURL) {
|
|
15814
|
+
return buildOpenAiEndpoint(baseURL, "models");
|
|
15815
|
+
}
|
|
15816
|
+
function buildOpenAiEndpoint(baseURL, path5) {
|
|
15766
15817
|
const url = new URL(baseURL);
|
|
15767
15818
|
const trimmed = url.pathname.replace(/\/+$/, "");
|
|
15768
|
-
url.pathname = trimmed
|
|
15819
|
+
url.pathname = /\/v\d+$/.test(trimmed) ? `${trimmed}/${path5}` : `${trimmed}/v1/${path5}`;
|
|
15769
15820
|
url.search = "";
|
|
15770
15821
|
url.hash = "";
|
|
15771
15822
|
return url.toString();
|
|
15772
15823
|
}
|
|
15824
|
+
function assertUpstreamOk(response) {
|
|
15825
|
+
if (response.status >= 300 && response.status < 400) {
|
|
15826
|
+
throw new ModelProviderServiceError(
|
|
15827
|
+
"UPSTREAM_REDIRECT_BLOCKED",
|
|
15828
|
+
502,
|
|
15829
|
+
"Upstream returned a redirect; configure the final URL directly"
|
|
15830
|
+
);
|
|
15831
|
+
}
|
|
15832
|
+
if (response.status === 401 || response.status === 403) {
|
|
15833
|
+
throw new ModelProviderServiceError(
|
|
15834
|
+
"UPSTREAM_AUTH_FAILED",
|
|
15835
|
+
502,
|
|
15836
|
+
"Upstream rejected the configured credentials"
|
|
15837
|
+
);
|
|
15838
|
+
}
|
|
15839
|
+
if (response.status === 429) {
|
|
15840
|
+
throw new ModelProviderServiceError(
|
|
15841
|
+
"UPSTREAM_RATE_LIMITED",
|
|
15842
|
+
502,
|
|
15843
|
+
"Upstream rate limited the request",
|
|
15844
|
+
true
|
|
15845
|
+
);
|
|
15846
|
+
}
|
|
15847
|
+
if (!response.ok) {
|
|
15848
|
+
throw new ModelProviderServiceError(
|
|
15849
|
+
"UPSTREAM_UNREACHABLE",
|
|
15850
|
+
502,
|
|
15851
|
+
"Upstream returned an error response",
|
|
15852
|
+
response.status >= 500
|
|
15853
|
+
);
|
|
15854
|
+
}
|
|
15855
|
+
}
|
|
15773
15856
|
function parseModelsPayload(payload) {
|
|
15774
15857
|
const entries = Array.isArray(payload) ? payload : typeof payload === "object" && payload !== null ? payload.data : void 0;
|
|
15775
15858
|
if (!Array.isArray(entries)) {
|
|
@@ -15840,36 +15923,60 @@ var openAiCompatibleAdapter = {
|
|
|
15840
15923
|
true
|
|
15841
15924
|
);
|
|
15842
15925
|
}
|
|
15843
|
-
|
|
15844
|
-
|
|
15845
|
-
|
|
15846
|
-
|
|
15847
|
-
|
|
15848
|
-
|
|
15849
|
-
}
|
|
15850
|
-
if (response.status === 401 || response.status === 403) {
|
|
15851
|
-
throw new ModelProviderServiceError(
|
|
15852
|
-
"UPSTREAM_AUTH_FAILED",
|
|
15853
|
-
502,
|
|
15854
|
-
"Upstream rejected the configured credentials"
|
|
15855
|
-
);
|
|
15856
|
-
}
|
|
15857
|
-
if (response.status === 429) {
|
|
15926
|
+
assertUpstreamOk(response);
|
|
15927
|
+
const body = await readBodyWithLimit(response, MAX_RESPONSE_BYTES);
|
|
15928
|
+
let payload;
|
|
15929
|
+
try {
|
|
15930
|
+
payload = JSON.parse(body);
|
|
15931
|
+
} catch {
|
|
15858
15932
|
throw new ModelProviderServiceError(
|
|
15859
|
-
"
|
|
15933
|
+
"UPSTREAM_INVALID_RESPONSE",
|
|
15860
15934
|
502,
|
|
15861
|
-
"Upstream
|
|
15862
|
-
true
|
|
15935
|
+
"Upstream response was not valid JSON"
|
|
15863
15936
|
);
|
|
15864
15937
|
}
|
|
15865
|
-
|
|
15938
|
+
return parseModelsPayload(payload);
|
|
15939
|
+
}
|
|
15940
|
+
};
|
|
15941
|
+
async function probeEmbeddingInference(input, signal) {
|
|
15942
|
+
const startedAt = Date.now();
|
|
15943
|
+
const endpoint = buildOpenAiEndpoint(input.baseURL, "embeddings");
|
|
15944
|
+
const headers = {
|
|
15945
|
+
Accept: "application/json",
|
|
15946
|
+
"Content-Type": "application/json"
|
|
15947
|
+
};
|
|
15948
|
+
if (input.apiKey) {
|
|
15949
|
+
headers.Authorization = `Bearer ${input.apiKey}`;
|
|
15950
|
+
}
|
|
15951
|
+
try {
|
|
15952
|
+
let response;
|
|
15953
|
+
try {
|
|
15954
|
+
response = await fetch(endpoint, {
|
|
15955
|
+
method: "POST",
|
|
15956
|
+
headers,
|
|
15957
|
+
// Never follow redirects: a redirected request could reach an address
|
|
15958
|
+
// that bypassed the SSRF checks.
|
|
15959
|
+
redirect: "manual",
|
|
15960
|
+
signal,
|
|
15961
|
+
body: JSON.stringify({ model: input.modelId, input: "ping" })
|
|
15962
|
+
});
|
|
15963
|
+
} catch (error) {
|
|
15964
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
15965
|
+
throw new ModelProviderServiceError(
|
|
15966
|
+
"UPSTREAM_TIMEOUT",
|
|
15967
|
+
504,
|
|
15968
|
+
"Upstream did not respond in time",
|
|
15969
|
+
true
|
|
15970
|
+
);
|
|
15971
|
+
}
|
|
15866
15972
|
throw new ModelProviderServiceError(
|
|
15867
15973
|
"UPSTREAM_UNREACHABLE",
|
|
15868
15974
|
502,
|
|
15869
|
-
"Upstream
|
|
15870
|
-
|
|
15975
|
+
"Upstream could not be reached",
|
|
15976
|
+
true
|
|
15871
15977
|
);
|
|
15872
15978
|
}
|
|
15979
|
+
assertUpstreamOk(response);
|
|
15873
15980
|
const body = await readBodyWithLimit(response, MAX_RESPONSE_BYTES);
|
|
15874
15981
|
let payload;
|
|
15875
15982
|
try {
|
|
@@ -15881,9 +15988,37 @@ var openAiCompatibleAdapter = {
|
|
|
15881
15988
|
"Upstream response was not valid JSON"
|
|
15882
15989
|
);
|
|
15883
15990
|
}
|
|
15884
|
-
return
|
|
15991
|
+
return {
|
|
15992
|
+
reachable: true,
|
|
15993
|
+
latencyMs: Date.now() - startedAt,
|
|
15994
|
+
dimensions: readEmbeddingDimensions(payload)
|
|
15995
|
+
};
|
|
15996
|
+
} catch (error) {
|
|
15997
|
+
if (error instanceof ModelProviderServiceError && isUpstreamErrorCode(error.code)) {
|
|
15998
|
+
return {
|
|
15999
|
+
reachable: false,
|
|
16000
|
+
latencyMs: Date.now() - startedAt,
|
|
16001
|
+
error: error.code
|
|
16002
|
+
};
|
|
16003
|
+
}
|
|
16004
|
+
throw error;
|
|
15885
16005
|
}
|
|
15886
|
-
}
|
|
16006
|
+
}
|
|
16007
|
+
function readEmbeddingDimensions(payload) {
|
|
16008
|
+
if (typeof payload !== "object" || payload === null) {
|
|
16009
|
+
return void 0;
|
|
16010
|
+
}
|
|
16011
|
+
const data = payload.data;
|
|
16012
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
16013
|
+
return void 0;
|
|
16014
|
+
}
|
|
16015
|
+
const first = data[0];
|
|
16016
|
+
if (typeof first !== "object" || first === null) {
|
|
16017
|
+
return void 0;
|
|
16018
|
+
}
|
|
16019
|
+
const embedding = first.embedding;
|
|
16020
|
+
return Array.isArray(embedding) ? embedding.length : void 0;
|
|
16021
|
+
}
|
|
15887
16022
|
async function readBodyWithLimit(response, maxBytes) {
|
|
15888
16023
|
if (!response.body) {
|
|
15889
16024
|
return response.text();
|
|
@@ -15985,6 +16120,9 @@ var ModelProviderService = class {
|
|
|
15985
16120
|
}
|
|
15986
16121
|
const models = snapshot.map((model) => ({
|
|
15987
16122
|
modelKey: toModelKey(model.providerId, model.modelId),
|
|
16123
|
+
// What this row can do. Pickers filter on it, so it travels with every
|
|
16124
|
+
// entry rather than being re-derived by each consumer.
|
|
16125
|
+
kind: model.kind,
|
|
15988
16126
|
displayName: model.displayName,
|
|
15989
16127
|
providerId: model.providerId,
|
|
15990
16128
|
providerName: providerNames.get(model.providerId) ?? model.providerId,
|
|
@@ -16012,6 +16150,9 @@ var ModelProviderService = class {
|
|
|
16012
16150
|
// which is how the deployment's `default` alias came to be offered as
|
|
16013
16151
|
// a model literally called "default".
|
|
16014
16152
|
displayName: config.displayName || config.model || modelKey2,
|
|
16153
|
+
// Deployment-registered models are chat by construction: they are
|
|
16154
|
+
// built with `registerModelLattice`, which only mints LLM clients.
|
|
16155
|
+
kind: "chat",
|
|
16015
16156
|
providerId: "",
|
|
16016
16157
|
providerName: "registered",
|
|
16017
16158
|
source: "registered",
|
|
@@ -16065,6 +16206,8 @@ var ModelProviderService = class {
|
|
|
16065
16206
|
* left untouched.
|
|
16066
16207
|
*/
|
|
16067
16208
|
async reconcileProvider(tenantId, providerId2) {
|
|
16209
|
+
this.deps.registry.evictProviderCache?.(tenantId, providerId2);
|
|
16210
|
+
this.deps.registry.forgetTenantDefaultMiss?.(tenantId);
|
|
16068
16211
|
if (!this.shouldRegister(tenantId)) {
|
|
16069
16212
|
return;
|
|
16070
16213
|
}
|
|
@@ -16074,21 +16217,9 @@ var ModelProviderService = class {
|
|
|
16074
16217
|
}
|
|
16075
16218
|
const provider = await this.deps.providerStore.getProviderById(tenantId, providerId2);
|
|
16076
16219
|
if (!provider) {
|
|
16077
|
-
const prefix = `${providerId2}/`;
|
|
16078
|
-
for (const { modelKey: modelKey2 } of this.deps.registry.listRegistered(tenantId)) {
|
|
16079
|
-
if (modelKey2.startsWith(prefix)) {
|
|
16080
|
-
this.deps.registry.remove(tenantId, modelKey2);
|
|
16081
|
-
}
|
|
16082
|
-
}
|
|
16083
16220
|
return;
|
|
16084
16221
|
}
|
|
16085
16222
|
const models = await this.deps.providerStore.listModels(tenantId, providerId2);
|
|
16086
|
-
const current = new Set(models.map((model) => toModelKey(providerId2, model.modelId)));
|
|
16087
|
-
for (const { modelKey: modelKey2 } of this.deps.registry.listRegistered(tenantId)) {
|
|
16088
|
-
if (modelKey2.startsWith(`${providerId2}/`) && !current.has(modelKey2)) {
|
|
16089
|
-
this.deps.registry.remove(tenantId, modelKey2);
|
|
16090
|
-
}
|
|
16091
|
-
}
|
|
16092
16223
|
this.syncRegistry(tenantId, provider, models);
|
|
16093
16224
|
}
|
|
16094
16225
|
/**
|
|
@@ -16187,11 +16318,9 @@ var ModelProviderService = class {
|
|
|
16187
16318
|
async remove(userId, tenantId, id3) {
|
|
16188
16319
|
await this.assertAdmin(userId, tenantId);
|
|
16189
16320
|
await this.requireProvider(tenantId, id3);
|
|
16190
|
-
const snapshot = await this.deps.providerStore.listModels(tenantId, id3);
|
|
16191
16321
|
await this.deps.providerStore.deleteProvider(tenantId, id3);
|
|
16192
|
-
|
|
16193
|
-
|
|
16194
|
-
}
|
|
16322
|
+
this.deps.registry.evictProviderCache?.(tenantId, id3);
|
|
16323
|
+
this.deps.registry.forgetTenantDefaultMiss?.(tenantId);
|
|
16195
16324
|
this.notifyChanged(tenantId, id3);
|
|
16196
16325
|
}
|
|
16197
16326
|
/**
|
|
@@ -16221,7 +16350,8 @@ var ModelProviderService = class {
|
|
|
16221
16350
|
() => controller.abort(),
|
|
16222
16351
|
resolveDiscoveryTimeoutMs()
|
|
16223
16352
|
);
|
|
16224
|
-
const previous =
|
|
16353
|
+
const previous = await this.deps.providerStore.listModels(tenantId, id3);
|
|
16354
|
+
const previousIds = new Set(previous.map((model) => model.modelId));
|
|
16225
16355
|
let discovered;
|
|
16226
16356
|
try {
|
|
16227
16357
|
discovered = await adapter.discover({
|
|
@@ -16247,7 +16377,12 @@ var ModelProviderService = class {
|
|
|
16247
16377
|
const inserted = await this.deps.providerStore.replaceProviderModels(
|
|
16248
16378
|
tenantId,
|
|
16249
16379
|
id3,
|
|
16250
|
-
discovered
|
|
16380
|
+
discovered.map((model) => ({
|
|
16381
|
+
...model,
|
|
16382
|
+
// Same rule as `setModels`: a row this provider just brought in starts
|
|
16383
|
+
// with the default config, a row already stored keeps the tenant's.
|
|
16384
|
+
extra: previousIds.has(model.id) ? void 0 : { ...DEFAULT_MODEL_EXTRA }
|
|
16385
|
+
}))
|
|
16251
16386
|
);
|
|
16252
16387
|
await this.deps.providerStore.markProviderStatus(
|
|
16253
16388
|
tenantId,
|
|
@@ -16321,6 +16456,30 @@ var ModelProviderService = class {
|
|
|
16321
16456
|
resolveDiscoveryTimeoutMs()
|
|
16322
16457
|
);
|
|
16323
16458
|
const startedAt = Date.now();
|
|
16459
|
+
if (input.kind === "embedding") {
|
|
16460
|
+
if (!input.modelId) {
|
|
16461
|
+
throw new ModelProviderServiceError(
|
|
16462
|
+
"INVALID_INPUT",
|
|
16463
|
+
400,
|
|
16464
|
+
"An embedding probe needs a model id"
|
|
16465
|
+
);
|
|
16466
|
+
}
|
|
16467
|
+
try {
|
|
16468
|
+
const outcome = await probeEmbeddingInference(
|
|
16469
|
+
{ baseURL: input.baseURL, apiKey, modelId: input.modelId },
|
|
16470
|
+
controller.signal
|
|
16471
|
+
);
|
|
16472
|
+
return {
|
|
16473
|
+
reachable: outcome.reachable,
|
|
16474
|
+
latencyMs: outcome.latencyMs,
|
|
16475
|
+
models: [],
|
|
16476
|
+
...outcome.dimensions === void 0 ? {} : { dimensions: outcome.dimensions },
|
|
16477
|
+
...outcome.error ? { error: outcome.error } : {}
|
|
16478
|
+
};
|
|
16479
|
+
} finally {
|
|
16480
|
+
clearTimeout(timeout);
|
|
16481
|
+
}
|
|
16482
|
+
}
|
|
16324
16483
|
try {
|
|
16325
16484
|
const models = await adapter.discover({
|
|
16326
16485
|
baseURL: input.baseURL,
|
|
@@ -16385,8 +16544,13 @@ var ModelProviderService = class {
|
|
|
16385
16544
|
ownedBy: previousById.get(entry.modelId)?.ownedBy,
|
|
16386
16545
|
// An omitted `extra` means "keep what is stored" — the picker sends no
|
|
16387
16546
|
// config, and a sync must not wipe one set by hand. The store resolves
|
|
16388
|
-
// the fallback against the row's current value.
|
|
16389
|
-
|
|
16547
|
+
// the fallback against the row's current value. A model that is not
|
|
16548
|
+
// stored at all gets the default config rather than an empty one.
|
|
16549
|
+
extra: entry.extra ?? (previousById.has(entry.modelId) ? void 0 : { ...DEFAULT_MODEL_EXTRA }),
|
|
16550
|
+
// Omitted means "keep the stored kind"; the store defaults a new row
|
|
16551
|
+
// to chat. A picker that only toggles `enabled` therefore cannot
|
|
16552
|
+
// accidentally reclassify a model.
|
|
16553
|
+
kind: entry.kind,
|
|
16390
16554
|
// Same rule: omitted leaves the flag alone, so a sync cannot quietly
|
|
16391
16555
|
// move the tenant's default.
|
|
16392
16556
|
isDefault: entry.isDefault
|
|
@@ -16409,14 +16573,17 @@ var ModelProviderService = class {
|
|
|
16409
16573
|
return this.toPublicEntry(provider, models.length);
|
|
16410
16574
|
}
|
|
16411
16575
|
/**
|
|
16412
|
-
* Give a tenant's sole model the default flag.
|
|
16576
|
+
* Give a tenant's sole **chat** model the default flag.
|
|
16413
16577
|
*
|
|
16414
|
-
* A tenant with exactly one runnable model has an obvious default, and
|
|
16415
|
-
* so is what keeps the invariant the rest of the feature leans on: a
|
|
16416
|
-
* either has no
|
|
16417
|
-
* or has a default, which cannot be deleted. Without this a tenant
|
|
16418
|
-
* with one model and no default, and deleting that model would
|
|
16419
|
-
* behind.
|
|
16578
|
+
* A tenant with exactly one runnable chat model has an obvious default, and
|
|
16579
|
+
* saying so is what keeps the invariant the rest of the feature leans on: a
|
|
16580
|
+
* tenant either has no chat model at all — and falls back to the deployment's
|
|
16581
|
+
* default — or has a default, which cannot be deleted. Without this a tenant
|
|
16582
|
+
* could sit with one model and no default, and deleting that model would
|
|
16583
|
+
* leave nothing behind.
|
|
16584
|
+
*
|
|
16585
|
+
* Chat only: a bare `"default"` key resolves to a chat client, so an
|
|
16586
|
+
* embedding or ASR row must never be marked as one.
|
|
16420
16587
|
*
|
|
16421
16588
|
* Deliberately narrow: more than one model is the tenant's choice to make,
|
|
16422
16589
|
* and a disabled model is not a candidate for "the model this tenant runs".
|
|
@@ -16425,24 +16592,33 @@ var ModelProviderService = class {
|
|
|
16425
16592
|
*/
|
|
16426
16593
|
async ensureDefaultModel(tenantId) {
|
|
16427
16594
|
const models = await this.deps.providerStore.listModels(tenantId);
|
|
16428
|
-
|
|
16595
|
+
const chatModels = models.filter((model) => model.kind === "chat");
|
|
16596
|
+
if (chatModels.length !== 1) {
|
|
16429
16597
|
return;
|
|
16430
16598
|
}
|
|
16431
|
-
const [only] =
|
|
16599
|
+
const [only] = chatModels;
|
|
16432
16600
|
if (only.isDefault || !only.enabled) {
|
|
16433
16601
|
return;
|
|
16434
16602
|
}
|
|
16435
|
-
|
|
16436
|
-
|
|
16437
|
-
|
|
16438
|
-
|
|
16439
|
-
|
|
16440
|
-
|
|
16441
|
-
|
|
16442
|
-
|
|
16443
|
-
|
|
16603
|
+
const siblings = models.filter(
|
|
16604
|
+
(model) => model.providerId === only.providerId
|
|
16605
|
+
);
|
|
16606
|
+
await this.deps.providerStore.replaceProviderModels(
|
|
16607
|
+
tenantId,
|
|
16608
|
+
only.providerId,
|
|
16609
|
+
siblings.map((model) => ({
|
|
16610
|
+
id: model.modelId,
|
|
16611
|
+
enabled: model.enabled,
|
|
16612
|
+
stale: model.stale,
|
|
16613
|
+
extra: model.extra,
|
|
16614
|
+
kind: model.kind,
|
|
16615
|
+
isDefault: model.modelId === only.modelId
|
|
16616
|
+
}))
|
|
16617
|
+
);
|
|
16444
16618
|
}
|
|
16445
16619
|
syncRegistry(tenantId, provider, models) {
|
|
16620
|
+
this.deps.registry.evictProviderCache?.(tenantId, provider.id);
|
|
16621
|
+
this.deps.registry.forgetTenantDefaultMiss?.(tenantId);
|
|
16446
16622
|
if (!this.shouldRegister(tenantId)) {
|
|
16447
16623
|
return [];
|
|
16448
16624
|
}
|
|
@@ -16689,6 +16865,10 @@ var setModelsSchema = z9.object({
|
|
|
16689
16865
|
// Omitted means "leave the stored config alone" — the picker sends
|
|
16690
16866
|
// only ids and states, and must not wipe a config set by hand.
|
|
16691
16867
|
extra: extraSchema.optional(),
|
|
16868
|
+
// What this model does. Omitted means "leave the stored kind
|
|
16869
|
+
// alone"; a new row defaults to chat. The kinds are independent —
|
|
16870
|
+
// each has its own runtime lattice and loader guard.
|
|
16871
|
+
kind: z9.enum(["chat", "embedding", "asr"]).optional(),
|
|
16692
16872
|
// At most one per tenant; the service rejects a tie with a
|
|
16693
16873
|
// readable error rather than letting the unique index raise one.
|
|
16694
16874
|
// Omitted means "leave the stored flag alone", which is what a
|
|
@@ -16701,43 +16881,13 @@ var probeProviderSchema = z9.object({
|
|
|
16701
16881
|
baseURL: z9.string().trim().min(1).max(2048),
|
|
16702
16882
|
apiKey: z9.string().max(1024).optional(),
|
|
16703
16883
|
providerId: z9.string().trim().min(1).max(255).optional(),
|
|
16704
|
-
protocol: protocolSchema.optional()
|
|
16884
|
+
protocol: protocolSchema.optional(),
|
|
16885
|
+
// Only an embedding probe uses these: it names the model and asks for one
|
|
16886
|
+
// inference, because a model list cannot prove a model can embed.
|
|
16887
|
+
kind: z9.enum(["chat", "embedding", "asr"]).optional(),
|
|
16888
|
+
modelId: z9.string().trim().min(1).max(512).optional()
|
|
16705
16889
|
}).strict();
|
|
16706
16890
|
|
|
16707
|
-
// src/controllers/tenant-scope.ts
|
|
16708
|
-
var TenantScopeError2 = class extends Error {
|
|
16709
|
-
/** Creates a tenant scope error. */
|
|
16710
|
-
constructor(code, statusCode, message) {
|
|
16711
|
-
super(message);
|
|
16712
|
-
this.code = code;
|
|
16713
|
-
this.statusCode = statusCode;
|
|
16714
|
-
this.name = "TenantScopeError";
|
|
16715
|
-
}
|
|
16716
|
-
};
|
|
16717
|
-
function resolveTenantScope(request, authRequired2) {
|
|
16718
|
-
const authenticated = request.user?.tenantId;
|
|
16719
|
-
const header2 = request.headers["x-tenant-id"];
|
|
16720
|
-
const requested = Array.isArray(header2) ? header2[0] : header2;
|
|
16721
|
-
if (authenticated) {
|
|
16722
|
-
if (requested && requested !== authenticated) {
|
|
16723
|
-
throw new TenantScopeError2(
|
|
16724
|
-
"TENANT_ACCESS_DENIED",
|
|
16725
|
-
403,
|
|
16726
|
-
"Tenant access denied"
|
|
16727
|
-
);
|
|
16728
|
-
}
|
|
16729
|
-
return { tenantId: authenticated, userId: request.user?.id };
|
|
16730
|
-
}
|
|
16731
|
-
if (authRequired2) {
|
|
16732
|
-
throw new TenantScopeError2(
|
|
16733
|
-
"UNAUTHENTICATED",
|
|
16734
|
-
401,
|
|
16735
|
-
"Console session required"
|
|
16736
|
-
);
|
|
16737
|
-
}
|
|
16738
|
-
return { tenantId: requested || "default" };
|
|
16739
|
-
}
|
|
16740
|
-
|
|
16741
16891
|
// src/controllers/model-providers.ts
|
|
16742
16892
|
var logger4 = new Logger({ serviceName: "lattice/gateway/model-providers" });
|
|
16743
16893
|
function sendError2(reply, error) {
|
|
@@ -23094,7 +23244,13 @@ var registerLatticeRoutes = (app2, channelDeps, options = {}) => {
|
|
|
23094
23244
|
config: lattice.client.getConfig()
|
|
23095
23245
|
}))
|
|
23096
23246
|
),
|
|
23097
|
-
forgetTenantDefaultMiss: (tenantId) => modelLatticeManager3.forgetTenantDefaultMiss(tenantId)
|
|
23247
|
+
forgetTenantDefaultMiss: (tenantId) => modelLatticeManager3.forgetTenantDefaultMiss(tenantId),
|
|
23248
|
+
evictProviderCache: (tenantId, providerId2) => {
|
|
23249
|
+
modelLatticeManager3.evictProviderCache(tenantId, providerId2);
|
|
23250
|
+
embeddingsLatticeManager2.evictProviderCache(tenantId, providerId2);
|
|
23251
|
+
sttModelLatticeManager2.evictProviderCache(tenantId, providerId2);
|
|
23252
|
+
vectorStoreLatticeManager.clearTenantVectorStores(tenantId);
|
|
23253
|
+
}
|
|
23098
23254
|
},
|
|
23099
23255
|
onChanged: (tenantId, providerId2) => broadcastModelProviderChanged(tenantId, providerId2)
|
|
23100
23256
|
}, { authRequired: process.env.AUTH_REQUIRED === "true" });
|
|
@@ -25153,7 +25309,7 @@ import { setBindingRegistry, setMenuRegistry, setEvalRunService } from "@axiom-l
|
|
|
25153
25309
|
|
|
25154
25310
|
// src/channels/wecom/WecomChannelAdapter.ts
|
|
25155
25311
|
import { randomUUID as randomUUID25 } from "crypto";
|
|
25156
|
-
import { z as
|
|
25312
|
+
import { z as z13 } from "zod";
|
|
25157
25313
|
import * as WecomAiBot from "@wecom/aibot-node-sdk";
|
|
25158
25314
|
|
|
25159
25315
|
// src/channels/wecom/parser.ts
|
|
@@ -25178,23 +25334,287 @@ function parseWecomTextFrame(frame) {
|
|
|
25178
25334
|
};
|
|
25179
25335
|
}
|
|
25180
25336
|
|
|
25337
|
+
// src/channels/wecom/cardTool.ts
|
|
25338
|
+
import { getToolLattice, registerToolLattice } from "@axiom-lattice/core";
|
|
25339
|
+
import { z as z12 } from "zod";
|
|
25340
|
+
var CARD_TTL_MS = 10 * 6e4;
|
|
25341
|
+
var MAX_CARD_ENTRIES = 1e3;
|
|
25342
|
+
var cardEntries = /* @__PURE__ */ new Map();
|
|
25343
|
+
function pruneCards(now) {
|
|
25344
|
+
for (const [key, entry] of cardEntries) {
|
|
25345
|
+
if (entry.expiresAt <= now) cardEntries.delete(key);
|
|
25346
|
+
}
|
|
25347
|
+
while (cardEntries.size > MAX_CARD_ENTRIES) {
|
|
25348
|
+
const oldest = cardEntries.keys().next().value;
|
|
25349
|
+
if (oldest === void 0) break;
|
|
25350
|
+
cardEntries.delete(oldest);
|
|
25351
|
+
}
|
|
25352
|
+
}
|
|
25353
|
+
function putWecomCard(key, card) {
|
|
25354
|
+
const now = Date.now();
|
|
25355
|
+
pruneCards(now);
|
|
25356
|
+
if (cardEntries.get(key)?.closed) return false;
|
|
25357
|
+
cardEntries.delete(key);
|
|
25358
|
+
cardEntries.set(key, { card, expiresAt: now + CARD_TTL_MS, closed: false });
|
|
25359
|
+
return true;
|
|
25360
|
+
}
|
|
25361
|
+
function takeWecomCard(key) {
|
|
25362
|
+
const now = Date.now();
|
|
25363
|
+
pruneCards(now);
|
|
25364
|
+
const entry = cardEntries.get(key);
|
|
25365
|
+
if (!entry) return void 0;
|
|
25366
|
+
cardEntries.delete(key);
|
|
25367
|
+
cardEntries.set(key, { expiresAt: now + CARD_TTL_MS, closed: true });
|
|
25368
|
+
return entry.card;
|
|
25369
|
+
}
|
|
25370
|
+
function discardWecomCard(key) {
|
|
25371
|
+
const now = Date.now();
|
|
25372
|
+
pruneCards(now);
|
|
25373
|
+
const entry = cardEntries.get(key);
|
|
25374
|
+
if (entry) entry.card = void 0;
|
|
25375
|
+
cardEntries.delete(key);
|
|
25376
|
+
cardEntries.set(key, { expiresAt: now + CARD_TTL_MS, closed: true });
|
|
25377
|
+
}
|
|
25378
|
+
var CARD_TYPES = [
|
|
25379
|
+
"text_notice",
|
|
25380
|
+
"news_notice",
|
|
25381
|
+
"button_interaction",
|
|
25382
|
+
"vote_interaction",
|
|
25383
|
+
"multiple_interaction"
|
|
25384
|
+
];
|
|
25385
|
+
var optionSchema = z12.object({
|
|
25386
|
+
id: z12.string().describe("\u9009\u9879 ID"),
|
|
25387
|
+
text: z12.string().describe("\u9009\u9879\u6587\u6848"),
|
|
25388
|
+
is_checked: z12.boolean().optional().describe("\u662F\u5426\u9ED8\u8BA4\u9009\u4E2D")
|
|
25389
|
+
});
|
|
25390
|
+
var schema = z12.object({
|
|
25391
|
+
card_type: z12.enum(CARD_TYPES).default("text_notice").describe(
|
|
25392
|
+
"\u5361\u7247\u7C7B\u578B\uFF1Atext_notice(\u6587\u672C\u901A\u77E5)/news_notice(\u56FE\u6587)/button_interaction(\u6309\u94AE)/vote_interaction(\u6295\u7968)/multiple_interaction(\u591A\u9009)"
|
|
25393
|
+
),
|
|
25394
|
+
title: z12.string().min(1).max(26).describe("\u4E00\u7EA7\u6807\u9898\uFF0C\u4E0D\u8D85\u8FC7 26 \u5B57"),
|
|
25395
|
+
desc: z12.string().max(30).optional().describe("\u6807\u9898\u8F85\u52A9\u4FE1\u606F\uFF0C\u4E0D\u8D85\u8FC7 30 \u5B57"),
|
|
25396
|
+
source_desc: z12.string().max(13).optional().describe("\u5361\u7247\u5DE6\u4E0A\u89D2\u6765\u6E90\u6587\u6848\uFF0C\u4E0D\u8D85\u8FC7 13 \u5B57"),
|
|
25397
|
+
source_color: z12.number().int().min(0).max(3).optional().describe("\u6765\u6E90\u6587\u5B57\u989C\u8272\uFF1A0 \u7070(\u9ED8\u8BA4)/1 \u9ED1/2 \u7EA2/3 \u7EFF"),
|
|
25398
|
+
emphasis_title: z12.string().max(10).optional().describe("\u5173\u952E\u6570\u5B57\uFF0C\u4E0D\u8D85\u8FC7 10 \u5B57"),
|
|
25399
|
+
emphasis_desc: z12.string().max(15).optional().describe("\u5173\u952E\u6570\u5B57\u8BF4\u660E\uFF0C\u4E0D\u8D85\u8FC7 15 \u5B57"),
|
|
25400
|
+
subtitle: z12.string().max(112).optional().describe("\u4E8C\u7EA7\u666E\u901A\u6587\u672C\uFF0C\u4E0D\u8D85\u8FC7 112 \u5B57"),
|
|
25401
|
+
quote_text: z12.string().optional().describe("\u5F15\u7528\u6837\u5F0F\u6587\u672C\uFF08\u4E0E\u5173\u952E\u6570\u5B57\u4E8C\u9009\u4E00\uFF09"),
|
|
25402
|
+
items: z12.array(z12.object({
|
|
25403
|
+
key: z12.string().max(5).describe("\u4E8C\u7EA7\u6807\u9898\uFF0C\u4E0D\u8D85\u8FC7 5 \u5B57"),
|
|
25404
|
+
value: z12.string().max(26).describe("\u4E8C\u7EA7\u6587\u672C\uFF0C\u4E0D\u8D85\u8FC7 26 \u5B57"),
|
|
25405
|
+
url: z12.string().optional().describe("\u70B9\u51FB\u8DF3\u8F6C\u94FE\u63A5\uFF08\u53EF\u9009\uFF09")
|
|
25406
|
+
})).max(6).optional().describe("\u952E\u503C\u660E\u7EC6\u5217\u8868\uFF0C\u6700\u591A 6 \u6761"),
|
|
25407
|
+
links: z12.array(z12.object({
|
|
25408
|
+
title: z12.string().max(13).describe("\u94FE\u63A5\u6587\u6848\uFF0C\u4E0D\u8D85\u8FC7 13 \u5B57"),
|
|
25409
|
+
url: z12.string()
|
|
25410
|
+
})).max(3).optional().describe("\u8DF3\u8F6C\u94FE\u63A5\u5217\u8868\uFF0C\u6700\u591A 3 \u6761"),
|
|
25411
|
+
action_url: z12.string().optional().describe("\u6574\u5F20\u5361\u7247\u70B9\u51FB\u8DF3\u8F6C\u7684\u94FE\u63A5\uFF08\u4F01\u5FAE\u8981\u6C42\u5FC5\u586B\uFF1B\u7F3A\u7701\u65F6\u81EA\u52A8\u53D6\u7B2C\u4E00\u6761 links[].url\uFF09"),
|
|
25412
|
+
// ── news_notice ──────────────────────────────────────────────
|
|
25413
|
+
image_url: z12.string().optional().describe("\u5361\u7247\u56FE\u7247\uFF08news_notice\uFF09"),
|
|
25414
|
+
image_text: z12.object({ title: z12.string(), desc: z12.string().optional(), url: z12.string().optional() }).optional().describe("\u5DE6\u56FE\u53F3\u6587\u533A\u57DF\uFF08news_notice\uFF09"),
|
|
25415
|
+
vertical_items: z12.array(z12.object({ title: z12.string(), desc: z12.string().optional() })).max(4).optional().describe("\u5782\u76F4\u5185\u5BB9\u5217\u8868\uFF0C\u6700\u591A 4 \u6761\uFF08news_notice\uFF09"),
|
|
25416
|
+
// ── button_interaction ───────────────────────────────────────
|
|
25417
|
+
buttons: z12.array(z12.object({
|
|
25418
|
+
text: z12.string().max(4).describe("\u6309\u94AE\u6587\u6848\uFF0C\u4E0D\u8D85\u8FC7 4 \u5B57"),
|
|
25419
|
+
key: z12.string().describe("\u6309\u94AE key\uFF0C\u7528\u4E8E\u56DE\u8C03"),
|
|
25420
|
+
style: z12.number().int().min(1).max(4).optional().describe("\u6309\u94AE\u6837\u5F0F 1-4")
|
|
25421
|
+
})).max(6).optional().describe("\u6309\u94AE\u5217\u8868\uFF0C\u6700\u591A 6 \u6761\uFF08button_interaction\uFF09"),
|
|
25422
|
+
// ── vote_interaction ─────────────────────────────────────────
|
|
25423
|
+
checkbox: z12.object({
|
|
25424
|
+
question_key: z12.string().describe("\u9009\u62E9\u9898 key"),
|
|
25425
|
+
option_list: z12.array(optionSchema).max(20).describe("\u9009\u9879\u5217\u8868\uFF0C\u6700\u591A 20 \u4E2A")
|
|
25426
|
+
}).optional().describe("\u9009\u62E9\u9898\uFF08vote_interaction\uFF09"),
|
|
25427
|
+
submit_text: z12.string().optional().describe("\u63D0\u4EA4\u6309\u94AE\u6587\u6848\uFF0C\u9ED8\u8BA4\u300C\u63D0\u4EA4\u300D"),
|
|
25428
|
+
// ── multiple_interaction ─────────────────────────────────────
|
|
25429
|
+
selections: z12.array(z12.object({
|
|
25430
|
+
question_key: z12.string(),
|
|
25431
|
+
title: z12.string().optional(),
|
|
25432
|
+
option_list: z12.array(optionSchema).max(20)
|
|
25433
|
+
})).max(3).optional().describe("\u4E0B\u62C9\u9009\u62E9\u5668\u5217\u8868\uFF0C\u6700\u591A 3 \u4E2A\uFF08multiple_interaction\uFF09")
|
|
25434
|
+
});
|
|
25435
|
+
function assertUrl(url, field) {
|
|
25436
|
+
if (url === void 0) return;
|
|
25437
|
+
if (!/^https?:\/\/\S+$/i.test(url)) {
|
|
25438
|
+
throw new Error(`${field} \u5FC5\u987B\u662F http(s):// \u5F00\u5934\u7684\u53EF\u8BBF\u95EE\u94FE\u63A5\uFF0C\u6536\u5230\u300C${url}\u300D`);
|
|
25439
|
+
}
|
|
25440
|
+
}
|
|
25441
|
+
function newTaskId() {
|
|
25442
|
+
return `wecom-card-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
25443
|
+
}
|
|
25444
|
+
function buildTemplateCard(input) {
|
|
25445
|
+
if (!input.title.trim()) throw new Error("title \u4E0D\u80FD\u4E3A\u7A7A");
|
|
25446
|
+
assertUrl(input.action_url, "action_url");
|
|
25447
|
+
assertUrl(input.image_url, "image_url");
|
|
25448
|
+
input.links?.forEach((link, i) => assertUrl(link.url, `links[${i}].url`));
|
|
25449
|
+
input.items?.forEach((item, i) => assertUrl(item.url, `items[${i}].url`));
|
|
25450
|
+
assertUrl(input.image_text?.url, "image_text.url");
|
|
25451
|
+
const actionUrl = input.action_url ?? input.links?.[0]?.url;
|
|
25452
|
+
if (!actionUrl) {
|
|
25453
|
+
throw new Error(
|
|
25454
|
+
"\u5361\u7247\u5FC5\u987B\u63D0\u4F9B action_url\uFF08\u6574\u5361\u70B9\u51FB\u8DF3\u8F6C\uFF09\uFF0C\u6216\u81F3\u5C11\u4E00\u6761\u5E26 url \u7684 links \u2014\u2014 \u4F01\u5FAE\u8981\u6C42 card_action \u4E0D\u80FD\u4E3A\u7A7A"
|
|
25455
|
+
);
|
|
25456
|
+
}
|
|
25457
|
+
const card = {
|
|
25458
|
+
card_type: input.card_type,
|
|
25459
|
+
main_title: {
|
|
25460
|
+
title: input.title,
|
|
25461
|
+
...input.desc ? { desc: input.desc } : {}
|
|
25462
|
+
},
|
|
25463
|
+
card_action: { type: 1, url: actionUrl }
|
|
25464
|
+
};
|
|
25465
|
+
if (input.source_desc) {
|
|
25466
|
+
card.source = {
|
|
25467
|
+
desc: input.source_desc,
|
|
25468
|
+
...input.source_color !== void 0 ? { desc_color: input.source_color } : {}
|
|
25469
|
+
};
|
|
25470
|
+
}
|
|
25471
|
+
if (input.emphasis_title || input.emphasis_desc) {
|
|
25472
|
+
card.emphasis_content = {
|
|
25473
|
+
...input.emphasis_title ? { title: input.emphasis_title } : {},
|
|
25474
|
+
...input.emphasis_desc ? { desc: input.emphasis_desc } : {}
|
|
25475
|
+
};
|
|
25476
|
+
}
|
|
25477
|
+
if (input.subtitle) card.sub_title_text = input.subtitle;
|
|
25478
|
+
if (input.quote_text) card.quote_area = { type: 0, quote_text: input.quote_text };
|
|
25479
|
+
if (input.items?.length) {
|
|
25480
|
+
card.horizontal_content_list = input.items.map((item) => ({
|
|
25481
|
+
type: item.url ? 1 : 0,
|
|
25482
|
+
keyname: item.key,
|
|
25483
|
+
value: item.value,
|
|
25484
|
+
...item.url ? { url: item.url } : {}
|
|
25485
|
+
}));
|
|
25486
|
+
}
|
|
25487
|
+
if (input.links?.length) {
|
|
25488
|
+
card.jump_list = input.links.map((link) => ({
|
|
25489
|
+
type: 1,
|
|
25490
|
+
url: link.url,
|
|
25491
|
+
title: link.title
|
|
25492
|
+
}));
|
|
25493
|
+
}
|
|
25494
|
+
if (input.card_type === "news_notice") {
|
|
25495
|
+
if (input.image_url) card.card_image = { url: input.image_url };
|
|
25496
|
+
if (input.image_text) {
|
|
25497
|
+
card.image_text_area = {
|
|
25498
|
+
type: input.image_text.url ? 1 : 0,
|
|
25499
|
+
...input.image_text.url ? { url: input.image_text.url } : {},
|
|
25500
|
+
title: input.image_text.title,
|
|
25501
|
+
...input.image_text.desc ? { desc: input.image_text.desc } : {}
|
|
25502
|
+
};
|
|
25503
|
+
}
|
|
25504
|
+
if (input.vertical_items?.length) {
|
|
25505
|
+
card.vertical_content_list = input.vertical_items.map((item) => ({
|
|
25506
|
+
title: item.title,
|
|
25507
|
+
...item.desc ? { desc: item.desc } : {}
|
|
25508
|
+
}));
|
|
25509
|
+
}
|
|
25510
|
+
}
|
|
25511
|
+
if (input.card_type === "button_interaction") {
|
|
25512
|
+
if (!input.buttons?.length) throw new Error("button_interaction \u5FC5\u987B\u63D0\u4F9B buttons");
|
|
25513
|
+
card.button_list = input.buttons.map((button) => ({
|
|
25514
|
+
text: button.text,
|
|
25515
|
+
key: button.key,
|
|
25516
|
+
...button.style !== void 0 ? { style: button.style } : {}
|
|
25517
|
+
}));
|
|
25518
|
+
card.task_id = newTaskId();
|
|
25519
|
+
}
|
|
25520
|
+
if (input.card_type === "vote_interaction") {
|
|
25521
|
+
if (!input.checkbox) throw new Error("vote_interaction \u5FC5\u987B\u63D0\u4F9B checkbox");
|
|
25522
|
+
card.checkbox = {
|
|
25523
|
+
question_key: input.checkbox.question_key,
|
|
25524
|
+
option_list: input.checkbox.option_list.map((option) => ({
|
|
25525
|
+
id: option.id,
|
|
25526
|
+
text: option.text,
|
|
25527
|
+
...option.is_checked !== void 0 ? { is_checked: option.is_checked } : {}
|
|
25528
|
+
}))
|
|
25529
|
+
};
|
|
25530
|
+
card.submit_button = { text: input.submit_text ?? "\u63D0\u4EA4", type: 1, key: "submit" };
|
|
25531
|
+
card.task_id = newTaskId();
|
|
25532
|
+
}
|
|
25533
|
+
if (input.card_type === "multiple_interaction") {
|
|
25534
|
+
if (!input.selections?.length) throw new Error("multiple_interaction \u5FC5\u987B\u63D0\u4F9B selections");
|
|
25535
|
+
card.select_list = input.selections.map((selection) => ({
|
|
25536
|
+
question_key: selection.question_key,
|
|
25537
|
+
...selection.title ? { title: selection.title } : {},
|
|
25538
|
+
option_list: selection.option_list.map((option) => ({
|
|
25539
|
+
id: option.id,
|
|
25540
|
+
text: option.text,
|
|
25541
|
+
...option.is_checked !== void 0 ? { is_checked: option.is_checked } : {}
|
|
25542
|
+
}))
|
|
25543
|
+
}));
|
|
25544
|
+
card.submit_button = { text: input.submit_text ?? "\u63D0\u4EA4", type: 1, key: "submit" };
|
|
25545
|
+
card.task_id = newTaskId();
|
|
25546
|
+
}
|
|
25547
|
+
return card;
|
|
25548
|
+
}
|
|
25549
|
+
function readReplyKey(exeConfig) {
|
|
25550
|
+
const configurable = exeConfig?.configurable;
|
|
25551
|
+
const key = configurable?.runConfig?.wecomReplyKey;
|
|
25552
|
+
return typeof key === "string" && key.length > 0 ? key : void 0;
|
|
25553
|
+
}
|
|
25554
|
+
function registerWecomCardTool() {
|
|
25555
|
+
if (getToolLattice("wecom_card")) return;
|
|
25556
|
+
registerToolLattice(
|
|
25557
|
+
"wecom_card",
|
|
25558
|
+
{
|
|
25559
|
+
name: "wecom_card",
|
|
25560
|
+
description: "\u5728**\u4F01\u4E1A\u5FAE\u4FE1\u4F1A\u8BDD**\u4E2D\uFF0C\u628A\u672C\u8F6E\u56DE\u590D\u7684\u6838\u5FC3\u7ED3\u679C\u505A\u6210\u4E00\u5F20\u6A21\u677F\u5361\u7247\uFF0C\u5361\u7247\u4F1A\u5728\u672C\u6761\u56DE\u590D\u540E\u7531\u6E20\u9053\u81EA\u52A8\u53D1\u9001\u3002\n\n\u4E3B\u52A8\u4F7F\u7528\uFF08\u7528\u6237\u4E0D\u9700\u8981\u8BF4\u300C\u5361\u7247\u300D\u300C\u5361\u7247\u6D88\u606F\u300D\uFF09\uFF1A\u5F53\u56DE\u590D\u7684\u4E3B\u8981\u4EF7\u503C\u662F\u7ED3\u6784\u5316\u4E1A\u52A1\u5BF9\u8C61\u65F6 \u2014\u2014 \u5BA2\u6237\u4EA4\u63A5/\u6D3E\u5355\u3001\u544A\u8B66/\u5F02\u5E38/\u98CE\u9669\u3001\u6E05\u5355/\u6838\u5BF9\u8868\u3001\u5F85\u529E/\u884C\u52A8\u9879\u3001\u72B6\u6001/\u8FDB\u5EA6/\u6307\u6807\u6C47\u603B\u3001\u6295\u7968/\u9009\u62E9/\u786E\u8BA4\u3001\u9700\u8981\u70B9\u51FB\u8FDB\u5165\u7684\u94FE\u63A5/\u516C\u544A\u5165\u53E3\u3002\u53EA\u8981\u7ED3\u679C\u5177\u5907\u300C\u6807\u9898 + 1-6 \u6761\u952E\u503C\u660E\u7EC6\uFF08\u6216\u9009\u9879\uFF09+ \u4E00\u4E2A\u53EF\u8DF3\u8F6C\u94FE\u63A5\u300D\uFF0C\u5C31\u5E94\u8C03\u7528\u3002\n\n\u4E0D\u8981\u4F7F\u7528\uFF1A\u666E\u901A\u95EE\u7B54\u3001\u89E3\u91CA\u3001\u95F2\u804A\u3001\u957F\u6587\u5206\u6790\u3001\u4EE3\u7801\u3001\u6CA1\u6709\u660E\u786E\u8DF3\u8F6C\u76EE\u6807\u7684\u5BF9\u8BDD\u3001\u4E00\u53E5\u8BDD\u5C31\u80FD\u8BF4\u6E05\u7684\u7ED3\u679C\u3002\n\n\u8C03\u7528\u540E\u4ECD\u7528\u4E00\u4E24\u53E5\u8BDD\u603B\u7ED3\u7ED3\u8BBA\uFF1B\u4E0D\u8981\u518D\u8F93\u51FA JSON\uFF0C\u4E5F\u4E0D\u8981\u5199\u3010\u5361\u7247\u6D88\u606F\u3011\u3002\u6BCF\u6761\u56DE\u590D\u6700\u591A\u4E00\u5F20\u5361\u7247\uFF0C\u91CD\u590D\u8C03\u7528\u4F1A\u66FF\u6362\u4E0A\u4E00\u5F20\u3002\u8FD4\u56DE ERROR \u65F6\u6309\u63D0\u793A\u4FEE\u6B63\u540E\u91CD\u8BD5\uFF0C\u4E0D\u8981\u628A ERROR \u653E\u8FDB\u56DE\u590D\u3002\u5B50 agent \u4E0D\u8981\u8C03\u7528\u672C\u5DE5\u5177\u3002",
|
|
25561
|
+
schema
|
|
25562
|
+
},
|
|
25563
|
+
async (input, exeConfig) => {
|
|
25564
|
+
const replyKey = readReplyKey(exeConfig);
|
|
25565
|
+
if (!replyKey) {
|
|
25566
|
+
return "ERROR: wecom_card \u53EA\u80FD\u5728\u4F01\u4E1A\u5FAE\u4FE1\u5B9E\u65F6\u4F1A\u8BDD\u4E2D\u4F7F\u7528\uFF0C\u8BF7\u76F4\u63A5\u7528\u6587\u5B57\u56DE\u590D\u3002";
|
|
25567
|
+
}
|
|
25568
|
+
try {
|
|
25569
|
+
const card = buildTemplateCard(input);
|
|
25570
|
+
if (!putWecomCard(replyKey, card)) {
|
|
25571
|
+
return "ERROR: \u672C\u6761\u56DE\u590D\u5DF2\u7ED3\u675F\uFF0C\u65E0\u6CD5\u518D\u9644\u5E26\u5361\u7247\u3002\u8BF7\u628A\u7ED3\u679C\u5199\u5165\u6B63\u6587\u3002";
|
|
25572
|
+
}
|
|
25573
|
+
const title = card.main_title?.title;
|
|
25574
|
+
const suffix = typeof title === "string" && title ? `\u300C${title}\u300D` : "";
|
|
25575
|
+
return `\u5DF2\u751F\u6210\u4F01\u4E1A\u5FAE\u4FE1\u5361\u7247${suffix}\uFF0C\u5C06\u968F\u672C\u6761\u56DE\u590D\u81EA\u52A8\u53D1\u9001\u3002\u8BF7\u7528\u6587\u5B57\u603B\u7ED3\u7ED3\u8BBA\uFF0C\u4E0D\u8981\u91CD\u590D\u5361\u7247\u5185\u5BB9\u3002`;
|
|
25576
|
+
} catch (error) {
|
|
25577
|
+
return `ERROR: ${error instanceof Error ? error.message : String(error)}\u3002\u8BF7\u4FEE\u6B63\u53C2\u6570\u540E\u91CD\u65B0\u8C03\u7528 wecom_card\u3002`;
|
|
25578
|
+
}
|
|
25579
|
+
}
|
|
25580
|
+
);
|
|
25581
|
+
}
|
|
25582
|
+
|
|
25181
25583
|
// src/channels/wecom/WecomChannelAdapter.ts
|
|
25182
25584
|
var logger6 = new Logger({ serviceName: "lattice/gateway/wecom" });
|
|
25183
|
-
var wecomConfigSchema =
|
|
25184
|
-
botId:
|
|
25185
|
-
secret:
|
|
25585
|
+
var wecomConfigSchema = z13.object({
|
|
25586
|
+
botId: z13.string(),
|
|
25587
|
+
secret: z13.string()
|
|
25186
25588
|
});
|
|
25187
25589
|
var RECONNECT_BASE_MS = 5e3;
|
|
25188
25590
|
var RECONNECT_MAX_MS = 6e4;
|
|
25591
|
+
var SENT_CARD_TTL_MS = 10 * 6e4;
|
|
25592
|
+
var MAX_SENT_CARDS = 1e3;
|
|
25189
25593
|
var activeConnections2 = /* @__PURE__ */ new Map();
|
|
25190
25594
|
var streamIdsByReqId = /* @__PURE__ */ new Map();
|
|
25191
25595
|
var MAX_TRACKED_STREAMS = 1e3;
|
|
25192
|
-
|
|
25193
|
-
|
|
25194
|
-
|
|
25596
|
+
var sentCardsByTaskId = /* @__PURE__ */ new Map();
|
|
25597
|
+
var handledCardEventKeys = /* @__PURE__ */ new Map();
|
|
25598
|
+
var ACKNOWLEDGED_TTL_MS = 10 * 6e4;
|
|
25599
|
+
function isRecord4(value) {
|
|
25600
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
25601
|
+
}
|
|
25602
|
+
function replyKeyOf(installationId, reqId) {
|
|
25603
|
+
return `${installationId}:${reqId ?? "no-req"}`;
|
|
25604
|
+
}
|
|
25605
|
+
function replyKeyForTarget(replyTarget) {
|
|
25606
|
+
const frame = replyTarget.rawTarget?.frame;
|
|
25607
|
+
return replyKeyOf(replyTarget.channelInstallationId, frame?.headers?.req_id);
|
|
25608
|
+
}
|
|
25609
|
+
function isEventReplyTarget(replyTarget) {
|
|
25610
|
+
return replyTarget.rawTarget?.kind === "template_card_event";
|
|
25611
|
+
}
|
|
25612
|
+
function eventChatId(replyTarget) {
|
|
25613
|
+
const chatId = replyTarget.rawTarget?.chatId;
|
|
25614
|
+
return typeof chatId === "string" && chatId.length > 0 ? chatId : void 0;
|
|
25195
25615
|
}
|
|
25196
25616
|
function streamIdFor(replyTarget) {
|
|
25197
|
-
const key =
|
|
25617
|
+
const key = replyKeyForTarget(replyTarget);
|
|
25198
25618
|
let streamId = streamIdsByReqId.get(key);
|
|
25199
25619
|
if (!streamId) {
|
|
25200
25620
|
streamId = randomUUID25();
|
|
@@ -25206,6 +25626,267 @@ function streamIdFor(replyTarget) {
|
|
|
25206
25626
|
}
|
|
25207
25627
|
return streamId;
|
|
25208
25628
|
}
|
|
25629
|
+
function rememberSentCard(card) {
|
|
25630
|
+
const taskId = typeof card.task_id === "string" ? card.task_id : void 0;
|
|
25631
|
+
if (!taskId) return;
|
|
25632
|
+
const now = Date.now();
|
|
25633
|
+
for (const [key, entry] of sentCardsByTaskId) {
|
|
25634
|
+
if (entry.expiresAt <= now) sentCardsByTaskId.delete(key);
|
|
25635
|
+
}
|
|
25636
|
+
sentCardsByTaskId.set(taskId, { card, expiresAt: now + SENT_CARD_TTL_MS });
|
|
25637
|
+
while (sentCardsByTaskId.size > MAX_SENT_CARDS) {
|
|
25638
|
+
const oldest = sentCardsByTaskId.keys().next().value;
|
|
25639
|
+
if (oldest === void 0) break;
|
|
25640
|
+
sentCardsByTaskId.delete(oldest);
|
|
25641
|
+
}
|
|
25642
|
+
}
|
|
25643
|
+
function sentCardForTask(taskId) {
|
|
25644
|
+
if (!taskId) return void 0;
|
|
25645
|
+
const entry = sentCardsByTaskId.get(taskId);
|
|
25646
|
+
if (!entry) return void 0;
|
|
25647
|
+
if (entry.expiresAt <= Date.now()) {
|
|
25648
|
+
sentCardsByTaskId.delete(taskId);
|
|
25649
|
+
return void 0;
|
|
25650
|
+
}
|
|
25651
|
+
return entry.card;
|
|
25652
|
+
}
|
|
25653
|
+
function claimCardEvent(taskId, eventKey) {
|
|
25654
|
+
if (!taskId) return true;
|
|
25655
|
+
const key = `${taskId}:${eventKey ?? ""}`;
|
|
25656
|
+
const now = Date.now();
|
|
25657
|
+
for (const [handled, expiresAt] of handledCardEventKeys) {
|
|
25658
|
+
if (expiresAt <= now) handledCardEventKeys.delete(handled);
|
|
25659
|
+
}
|
|
25660
|
+
if (handledCardEventKeys.has(key)) return false;
|
|
25661
|
+
handledCardEventKeys.set(key, now + ACKNOWLEDGED_TTL_MS);
|
|
25662
|
+
while (handledCardEventKeys.size > MAX_SENT_CARDS) {
|
|
25663
|
+
const oldest = handledCardEventKeys.keys().next().value;
|
|
25664
|
+
if (oldest === void 0) break;
|
|
25665
|
+
handledCardEventKeys.delete(oldest);
|
|
25666
|
+
}
|
|
25667
|
+
return true;
|
|
25668
|
+
}
|
|
25669
|
+
async function sendStreamFrame(connection, replyTarget, input) {
|
|
25670
|
+
if (isEventReplyTarget(replyTarget)) {
|
|
25671
|
+
await sendEventReply(connection, replyTarget, input);
|
|
25672
|
+
return;
|
|
25673
|
+
}
|
|
25674
|
+
const frame = replyTarget.rawTarget?.frame;
|
|
25675
|
+
if (!frame) {
|
|
25676
|
+
logger6.warn("WeCom reply target has no inbound frame, skipping", {
|
|
25677
|
+
installationId: replyTarget.channelInstallationId
|
|
25678
|
+
});
|
|
25679
|
+
return;
|
|
25680
|
+
}
|
|
25681
|
+
const report = (message, error) => logger6.warn(message, {
|
|
25682
|
+
installationId: replyTarget.channelInstallationId,
|
|
25683
|
+
error: error instanceof Error ? error.message : String(error)
|
|
25684
|
+
});
|
|
25685
|
+
const text = input.text || (input.done ? input.card ? "\u5DF2\u5B8C\u6210\uFF0C\u8BE6\u60C5\u89C1\u4E0B\u65B9\u5361\u7247\u3002" : "\u5904\u7406\u5B8C\u6210\u3002" : "");
|
|
25686
|
+
if (!text) return;
|
|
25687
|
+
try {
|
|
25688
|
+
await Promise.resolve(connection.client.reply(frame, {
|
|
25689
|
+
msgtype: "stream",
|
|
25690
|
+
stream: { id: streamIdFor(replyTarget), finish: input.done, content: text }
|
|
25691
|
+
}));
|
|
25692
|
+
} catch (error) {
|
|
25693
|
+
report("WeCom stream reply failed", error);
|
|
25694
|
+
}
|
|
25695
|
+
if (input.done && input.card) {
|
|
25696
|
+
try {
|
|
25697
|
+
await Promise.resolve(connection.client.reply(frame, {
|
|
25698
|
+
msgtype: "template_card",
|
|
25699
|
+
template_card: input.card
|
|
25700
|
+
}));
|
|
25701
|
+
rememberSentCard(input.card);
|
|
25702
|
+
logger6.info("WeCom card sent", {
|
|
25703
|
+
installationId: replyTarget.channelInstallationId,
|
|
25704
|
+
cardType: input.card.card_type
|
|
25705
|
+
});
|
|
25706
|
+
} catch (error) {
|
|
25707
|
+
report("WeCom card reply failed (text already sent)", error);
|
|
25708
|
+
}
|
|
25709
|
+
}
|
|
25710
|
+
}
|
|
25711
|
+
async function sendEventReply(connection, replyTarget, input) {
|
|
25712
|
+
if (!input.done) return;
|
|
25713
|
+
const chatId = eventChatId(replyTarget);
|
|
25714
|
+
if (!chatId) {
|
|
25715
|
+
logger6.warn("WeCom card event has no chat id, cannot reply", {
|
|
25716
|
+
installationId: replyTarget.channelInstallationId
|
|
25717
|
+
});
|
|
25718
|
+
return;
|
|
25719
|
+
}
|
|
25720
|
+
const sendMessage = connection.client.sendMessage;
|
|
25721
|
+
if (!sendMessage) {
|
|
25722
|
+
logger6.warn("WeCom client does not support sendMessage, cannot reply to card event", {
|
|
25723
|
+
installationId: replyTarget.channelInstallationId
|
|
25724
|
+
});
|
|
25725
|
+
return;
|
|
25726
|
+
}
|
|
25727
|
+
const report = (message, error) => logger6.warn(message, {
|
|
25728
|
+
installationId: replyTarget.channelInstallationId,
|
|
25729
|
+
error: error instanceof Error ? error.message : String(error)
|
|
25730
|
+
});
|
|
25731
|
+
if (input.card) {
|
|
25732
|
+
try {
|
|
25733
|
+
await Promise.resolve(sendMessage.call(connection.client, chatId, {
|
|
25734
|
+
msgtype: "template_card",
|
|
25735
|
+
template_card: input.card
|
|
25736
|
+
}));
|
|
25737
|
+
rememberSentCard(input.card);
|
|
25738
|
+
logger6.info("WeCom card sent", {
|
|
25739
|
+
installationId: replyTarget.channelInstallationId,
|
|
25740
|
+
cardType: input.card.card_type
|
|
25741
|
+
});
|
|
25742
|
+
} catch (error) {
|
|
25743
|
+
report("WeCom card event card failed", error);
|
|
25744
|
+
}
|
|
25745
|
+
return;
|
|
25746
|
+
}
|
|
25747
|
+
if (input.text) {
|
|
25748
|
+
try {
|
|
25749
|
+
await Promise.resolve(sendMessage.call(connection.client, chatId, {
|
|
25750
|
+
msgtype: "markdown",
|
|
25751
|
+
markdown: { content: input.text }
|
|
25752
|
+
}));
|
|
25753
|
+
} catch (error) {
|
|
25754
|
+
report("WeCom card event reply failed", error);
|
|
25755
|
+
}
|
|
25756
|
+
}
|
|
25757
|
+
}
|
|
25758
|
+
function acknowledgedCard(taskId, eventKey) {
|
|
25759
|
+
const desc = eventKey ? `\u5DF2\u6536\u5230\uFF1A${eventKey}` : "\u5DF2\u63D0\u4EA4\uFF0C\u6B63\u5728\u5904\u7406\u2026";
|
|
25760
|
+
const original = sentCardForTask(taskId);
|
|
25761
|
+
if (original) {
|
|
25762
|
+
const mainTitle = isRecord4(original.main_title) ? original.main_title : {};
|
|
25763
|
+
return { ...original, main_title: { ...mainTitle, desc } };
|
|
25764
|
+
}
|
|
25765
|
+
return {
|
|
25766
|
+
card_type: "text_notice",
|
|
25767
|
+
main_title: { title: "\u5DF2\u6536\u5230\u4F60\u7684\u64CD\u4F5C", desc },
|
|
25768
|
+
...taskId ? { task_id: taskId } : {}
|
|
25769
|
+
};
|
|
25770
|
+
}
|
|
25771
|
+
function cardEventFields(event) {
|
|
25772
|
+
const nested = isRecord4(event?.template_card_event) ? event.template_card_event : event;
|
|
25773
|
+
const source = nested ?? {};
|
|
25774
|
+
return {
|
|
25775
|
+
cardEvent: source,
|
|
25776
|
+
taskId: typeof source.task_id === "string" ? source.task_id : void 0,
|
|
25777
|
+
eventKey: typeof source.event_key === "string" ? source.event_key : void 0,
|
|
25778
|
+
cardType: typeof source.card_type === "string" ? source.card_type : void 0
|
|
25779
|
+
};
|
|
25780
|
+
}
|
|
25781
|
+
function describeError(error) {
|
|
25782
|
+
if (error instanceof Error) return error.message;
|
|
25783
|
+
if (typeof error === "string") return error;
|
|
25784
|
+
try {
|
|
25785
|
+
return JSON.stringify(error);
|
|
25786
|
+
} catch {
|
|
25787
|
+
return String(error);
|
|
25788
|
+
}
|
|
25789
|
+
}
|
|
25790
|
+
function cardEventInbound(frame, installation, input) {
|
|
25791
|
+
const body = frame.body;
|
|
25792
|
+
const { cardEvent, taskId, eventKey, cardType } = cardEventFields(body.event);
|
|
25793
|
+
const summary = eventKey ? `\u7528\u6237\u70B9\u51FB\u4E86\u5361\u7247\u6309\u94AE\u300C${eventKey}\u300D${taskId ? `\uFF08task_id=${taskId}\uFF09` : ""}` : `\u7528\u6237\u63D0\u4EA4\u4E86\u5361\u7247\u64CD\u4F5C${taskId ? `\uFF08task_id=${taskId}\uFF09` : ""}\uFF1A${JSON.stringify(cardEvent)}`;
|
|
25794
|
+
return {
|
|
25795
|
+
channel: "wecom",
|
|
25796
|
+
channelInstallationId: installation.id,
|
|
25797
|
+
tenantId: installation.tenantId,
|
|
25798
|
+
sender: { id: input.userId },
|
|
25799
|
+
content: {
|
|
25800
|
+
text: summary,
|
|
25801
|
+
metadata: {
|
|
25802
|
+
messageId: body.msgid,
|
|
25803
|
+
chatId: input.chatId,
|
|
25804
|
+
chatType: body.chattype === "group" ? "group" : "direct",
|
|
25805
|
+
taskId,
|
|
25806
|
+
eventKey,
|
|
25807
|
+
cardType,
|
|
25808
|
+
cardEvent,
|
|
25809
|
+
wecomReplyKey: replyKeyOf(installation.id, frame.headers.req_id)
|
|
25810
|
+
}
|
|
25811
|
+
},
|
|
25812
|
+
conversation: {
|
|
25813
|
+
id: input.chatId,
|
|
25814
|
+
type: body.chattype === "group" ? "group" : "direct"
|
|
25815
|
+
},
|
|
25816
|
+
replyTarget: {
|
|
25817
|
+
adapterChannel: "wecom",
|
|
25818
|
+
channelInstallationId: installation.id,
|
|
25819
|
+
rawTarget: { kind: "template_card_event", frame, chatId: input.chatId }
|
|
25820
|
+
}
|
|
25821
|
+
};
|
|
25822
|
+
}
|
|
25823
|
+
function handleTemplateCardEvent(rawFrame, installation, client, router) {
|
|
25824
|
+
const frame = rawFrame;
|
|
25825
|
+
const body = frame?.body;
|
|
25826
|
+
const event = body?.event;
|
|
25827
|
+
const reqId = frame?.headers?.req_id;
|
|
25828
|
+
if (!body || event?.eventtype !== "template_card_event" || typeof reqId !== "string") {
|
|
25829
|
+
return;
|
|
25830
|
+
}
|
|
25831
|
+
const userId = body.from?.userid;
|
|
25832
|
+
const chatId = typeof body.chatid === "string" && body.chatid ? body.chatid : userId;
|
|
25833
|
+
if (!userId || !chatId) {
|
|
25834
|
+
logger6.warn("WeCom card event missing user/chat id, skipping", {
|
|
25835
|
+
installationId: installation.id
|
|
25836
|
+
});
|
|
25837
|
+
return;
|
|
25838
|
+
}
|
|
25839
|
+
const { taskId, eventKey } = cardEventFields(event);
|
|
25840
|
+
if (!claimCardEvent(taskId, eventKey)) {
|
|
25841
|
+
logger6.info("WeCom duplicate card event ignored", {
|
|
25842
|
+
installationId: installation.id,
|
|
25843
|
+
taskId,
|
|
25844
|
+
eventKey
|
|
25845
|
+
});
|
|
25846
|
+
return;
|
|
25847
|
+
}
|
|
25848
|
+
const updateTemplateCard = client.updateTemplateCard;
|
|
25849
|
+
if (updateTemplateCard) {
|
|
25850
|
+
const updated = acknowledgedCard(taskId, eventKey);
|
|
25851
|
+
try {
|
|
25852
|
+
void Promise.resolve(updateTemplateCard.call(client, rawFrame, updated)).catch((error) => {
|
|
25853
|
+
logger6.warn("WeCom template card update failed", {
|
|
25854
|
+
installationId: installation.id,
|
|
25855
|
+
error: describeError(error)
|
|
25856
|
+
});
|
|
25857
|
+
});
|
|
25858
|
+
} catch (error) {
|
|
25859
|
+
logger6.warn("WeCom template card update failed", {
|
|
25860
|
+
installationId: installation.id,
|
|
25861
|
+
error: describeError(error)
|
|
25862
|
+
});
|
|
25863
|
+
}
|
|
25864
|
+
} else {
|
|
25865
|
+
logger6.warn("WeCom client does not support updateTemplateCard", {
|
|
25866
|
+
installationId: installation.id
|
|
25867
|
+
});
|
|
25868
|
+
}
|
|
25869
|
+
if (!router) return;
|
|
25870
|
+
logger6.info("WeCom card event dispatched", {
|
|
25871
|
+
installationId: installation.id,
|
|
25872
|
+
taskId,
|
|
25873
|
+
eventKey,
|
|
25874
|
+
chatId
|
|
25875
|
+
});
|
|
25876
|
+
try {
|
|
25877
|
+
void router.dispatch(cardEventInbound(frame, installation, { chatId, userId })).catch((error) => {
|
|
25878
|
+
logger6.error("WeCom card event dispatch error", {
|
|
25879
|
+
installationId: installation.id,
|
|
25880
|
+
error: describeError(error)
|
|
25881
|
+
});
|
|
25882
|
+
});
|
|
25883
|
+
} catch (error) {
|
|
25884
|
+
logger6.error("WeCom card event dispatch error", {
|
|
25885
|
+
installationId: installation.id,
|
|
25886
|
+
error: describeError(error)
|
|
25887
|
+
});
|
|
25888
|
+
}
|
|
25889
|
+
}
|
|
25209
25890
|
var wecomChannelAdapter = {
|
|
25210
25891
|
channel: "wecom",
|
|
25211
25892
|
configSchema: wecomConfigSchema,
|
|
@@ -25219,11 +25900,13 @@ var wecomChannelAdapter = {
|
|
|
25219
25900
|
sender: { id: parsed.fromUserId },
|
|
25220
25901
|
content: {
|
|
25221
25902
|
text: parsed.text,
|
|
25222
|
-
// `messageId` 供去重中间件使用(企业微信可能重投同一 msgid
|
|
25903
|
+
// `messageId` 供去重中间件使用(企业微信可能重投同一 msgid);
|
|
25904
|
+
// `wecomReplyKey` 供 `wecom_card` 工具登记本轮卡片。
|
|
25223
25905
|
metadata: {
|
|
25224
25906
|
messageId: parsed.msgId,
|
|
25225
25907
|
chatId: parsed.chatId,
|
|
25226
|
-
chatType: parsed.chatType
|
|
25908
|
+
chatType: parsed.chatType,
|
|
25909
|
+
wecomReplyKey: replyKeyOf(installation.id, parsed.reqId)
|
|
25227
25910
|
}
|
|
25228
25911
|
},
|
|
25229
25912
|
conversation: {
|
|
@@ -25247,14 +25930,12 @@ var wecomChannelAdapter = {
|
|
|
25247
25930
|
});
|
|
25248
25931
|
return;
|
|
25249
25932
|
}
|
|
25250
|
-
const key =
|
|
25251
|
-
|
|
25252
|
-
|
|
25253
|
-
|
|
25254
|
-
|
|
25255
|
-
|
|
25256
|
-
content: message.text
|
|
25257
|
-
}
|
|
25933
|
+
const key = replyKeyForTarget(replyTarget);
|
|
25934
|
+
const card = takeWecomCard(key);
|
|
25935
|
+
await sendStreamFrame(connection, replyTarget, {
|
|
25936
|
+
text: message.text,
|
|
25937
|
+
done: true,
|
|
25938
|
+
card
|
|
25258
25939
|
});
|
|
25259
25940
|
streamIdsByReqId.delete(key);
|
|
25260
25941
|
},
|
|
@@ -25273,15 +25954,21 @@ var wecomChannelAdapter = {
|
|
|
25273
25954
|
});
|
|
25274
25955
|
return;
|
|
25275
25956
|
}
|
|
25276
|
-
|
|
25277
|
-
|
|
25278
|
-
|
|
25279
|
-
|
|
25280
|
-
|
|
25281
|
-
|
|
25957
|
+
const key = replyKeyForTarget(replyTarget);
|
|
25958
|
+
let card;
|
|
25959
|
+
if (chunk.done) {
|
|
25960
|
+
if (chunk.reason !== void 0 && chunk.reason !== "completed") {
|
|
25961
|
+
discardWecomCard(key);
|
|
25962
|
+
} else {
|
|
25963
|
+
card = takeWecomCard(key);
|
|
25282
25964
|
}
|
|
25965
|
+
}
|
|
25966
|
+
await sendStreamFrame(connection, replyTarget, {
|
|
25967
|
+
text: chunk.text,
|
|
25968
|
+
done: chunk.done,
|
|
25969
|
+
card
|
|
25283
25970
|
});
|
|
25284
|
-
if (chunk.done) streamIdsByReqId.delete(
|
|
25971
|
+
if (chunk.done) streamIdsByReqId.delete(key);
|
|
25285
25972
|
},
|
|
25286
25973
|
resolveThreadId(message, binding) {
|
|
25287
25974
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -25315,6 +26002,9 @@ var wecomChannelAdapter = {
|
|
|
25315
26002
|
});
|
|
25316
26003
|
});
|
|
25317
26004
|
});
|
|
26005
|
+
client.on("event.template_card_event", (frame) => {
|
|
26006
|
+
handleTemplateCardEvent(frame, installation, client, router);
|
|
26007
|
+
});
|
|
25318
26008
|
client.on("authenticated", () => {
|
|
25319
26009
|
connection.attempts = 0;
|
|
25320
26010
|
});
|
|
@@ -25811,6 +26501,9 @@ import {
|
|
|
25811
26501
|
agentLatticeManager as agentLatticeManager5,
|
|
25812
26502
|
configurePluginCatalogSync,
|
|
25813
26503
|
PLUGIN_CATALOG_CHANGED_TOPIC,
|
|
26504
|
+
configureCollectionSync,
|
|
26505
|
+
COLLECTION_CHANGED_TOPIC,
|
|
26506
|
+
vectorStoreLatticeManager as vectorStoreLatticeManager2,
|
|
25814
26507
|
getSandboxPluginCatalog as getSandboxPluginCatalog3,
|
|
25815
26508
|
getSandBoxManager as getSandBoxManager4,
|
|
25816
26509
|
installPluginHost
|
|
@@ -25843,6 +26536,7 @@ var ChannelInstallationNotFoundError = class extends Error {
|
|
|
25843
26536
|
}
|
|
25844
26537
|
};
|
|
25845
26538
|
var DEFAULT_STREAM_PLACEHOLDER = "\u6B63\u5728\u5904\u7406\u2026";
|
|
26539
|
+
var DEFAULT_STREAM_FAILURE_TEXT = "\u5904\u7406\u672A\u5B8C\u6210\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002";
|
|
25846
26540
|
function streamPlaceholderFor(installation) {
|
|
25847
26541
|
const config = installation?.config;
|
|
25848
26542
|
if (config !== null && typeof config === "object" && !Array.isArray(config)) {
|
|
@@ -25851,6 +26545,14 @@ function streamPlaceholderFor(installation) {
|
|
|
25851
26545
|
}
|
|
25852
26546
|
return DEFAULT_STREAM_PLACEHOLDER;
|
|
25853
26547
|
}
|
|
26548
|
+
function streamFailureTextFor(installation) {
|
|
26549
|
+
const config = installation?.config;
|
|
26550
|
+
if (config !== null && typeof config === "object" && !Array.isArray(config)) {
|
|
26551
|
+
const value = config.streamFailureText;
|
|
26552
|
+
if (typeof value === "string") return value;
|
|
26553
|
+
}
|
|
26554
|
+
return DEFAULT_STREAM_FAILURE_TEXT;
|
|
26555
|
+
}
|
|
25854
26556
|
var InvalidChannelInstallationError = class extends Error {
|
|
25855
26557
|
constructor(message = "Channel installation is not valid for this message") {
|
|
25856
26558
|
super(message);
|
|
@@ -26262,7 +26964,13 @@ var _MessageRouter = class _MessageRouter {
|
|
|
26262
26964
|
if (cleaned || !event.consumedInputMessageIds.includes(inputMessageId)) return;
|
|
26263
26965
|
cleanup();
|
|
26264
26966
|
if (event.inputMessageId !== inputMessageId) return;
|
|
26265
|
-
|
|
26967
|
+
if (replyAdapter.sendStreamChunk && streamState.finished) return;
|
|
26968
|
+
if (replyAdapter.sendStreamChunk) streamState.finished = true;
|
|
26969
|
+
const delivery = replyAdapter.sendStreamChunk ? replyAdapter.sendStreamChunk(
|
|
26970
|
+
replyTarget,
|
|
26971
|
+
{ text: event.outputText, done: true, reason: "completed" },
|
|
26972
|
+
installation
|
|
26973
|
+
) : replyAdapter.sendReply(replyTarget, {
|
|
26266
26974
|
text: event.outputText,
|
|
26267
26975
|
metadata: { inputMessageId, outputMessageId: event.outputMessageId }
|
|
26268
26976
|
}, installation);
|
|
@@ -26386,17 +27094,34 @@ var _MessageRouter = class _MessageRouter {
|
|
|
26386
27094
|
const send = adapter.sendStreamChunk;
|
|
26387
27095
|
if (!send) return;
|
|
26388
27096
|
let started = false;
|
|
27097
|
+
let normalEnd = false;
|
|
26389
27098
|
let accumulated = "";
|
|
26390
27099
|
let lastSentText = "";
|
|
26391
27100
|
let lastSentAt = 0;
|
|
26392
27101
|
let sent = 0;
|
|
26393
27102
|
const warn = (event, error) => console.warn({ event, inputMessageId, error: error instanceof Error ? error.message : String(error) });
|
|
26394
|
-
const push = (text, done) => send.call(
|
|
27103
|
+
const push = (text, done, reason) => send.call(
|
|
27104
|
+
adapter,
|
|
27105
|
+
replyTarget,
|
|
27106
|
+
reason ? { text, done, reason } : { text, done },
|
|
27107
|
+
installation
|
|
27108
|
+
).catch((error) => warn("dispatch:stream:failed", error));
|
|
26395
27109
|
const placeholder = streamPlaceholderFor(installation);
|
|
27110
|
+
const closeWith = async (reason) => {
|
|
27111
|
+
if (state.finished) return;
|
|
27112
|
+
state.finished = true;
|
|
27113
|
+
const text = accumulated || (reason === "completed" ? "" : streamFailureTextFor(installation) || placeholder);
|
|
27114
|
+
await push(text, true, reason);
|
|
27115
|
+
};
|
|
27116
|
+
const timer = setTimeout(() => {
|
|
27117
|
+
void closeWith("timeout");
|
|
27118
|
+
}, _MessageRouter.STREAM_TIMEOUT_MS);
|
|
27119
|
+
timer.unref?.();
|
|
26396
27120
|
try {
|
|
26397
27121
|
if (placeholder) await push(placeholder, false);
|
|
26398
27122
|
const stream = agent.chunkStream(inputMessageId, []);
|
|
26399
27123
|
for await (const chunk of stream) {
|
|
27124
|
+
if (state.finished) break;
|
|
26400
27125
|
if (!started) {
|
|
26401
27126
|
if (chunk?.type === MessageChunkTypes4.HUMAN && chunk?.data?.id === inputMessageId) {
|
|
26402
27127
|
started = true;
|
|
@@ -26404,12 +27129,19 @@ var _MessageRouter = class _MessageRouter {
|
|
|
26404
27129
|
continue;
|
|
26405
27130
|
}
|
|
26406
27131
|
if (chunk?.type === MessageChunkTypes4.MESSAGE_COMPLETED || chunk?.type === MessageChunkTypes4.MESSAGE_FAILED || chunk?.type === MessageChunkTypes4.INTERRUPT) {
|
|
26407
|
-
if (chunk.type
|
|
26408
|
-
|
|
26409
|
-
|
|
26410
|
-
|
|
26411
|
-
|
|
26412
|
-
|
|
27132
|
+
if (chunk.type === MessageChunkTypes4.MESSAGE_COMPLETED) {
|
|
27133
|
+
normalEnd = true;
|
|
27134
|
+
const grace = setTimeout(
|
|
27135
|
+
() => {
|
|
27136
|
+
void closeWith("completed");
|
|
27137
|
+
},
|
|
27138
|
+
_MessageRouter.COMPLETION_GRACE_MS
|
|
27139
|
+
);
|
|
27140
|
+
grace.unref?.();
|
|
27141
|
+
} else if (chunk.type === MessageChunkTypes4.MESSAGE_FAILED) {
|
|
27142
|
+
await closeWith("failed");
|
|
27143
|
+
} else {
|
|
27144
|
+
await closeWith("interrupted");
|
|
26413
27145
|
}
|
|
26414
27146
|
break;
|
|
26415
27147
|
}
|
|
@@ -26429,7 +27161,11 @@ var _MessageRouter = class _MessageRouter {
|
|
|
26429
27161
|
}
|
|
26430
27162
|
} catch (error) {
|
|
26431
27163
|
warn("dispatch:stream:error", error);
|
|
27164
|
+
await closeWith("failed");
|
|
27165
|
+
} finally {
|
|
27166
|
+
clearTimeout(timer);
|
|
26432
27167
|
}
|
|
27168
|
+
if (!normalEnd) await closeWith("failed");
|
|
26433
27169
|
}
|
|
26434
27170
|
/**
|
|
26435
27171
|
* Projects a pending HITL interrupt into the room as an independent bot message.
|
|
@@ -26454,15 +27190,30 @@ var _MessageRouter = class _MessageRouter {
|
|
|
26454
27190
|
}
|
|
26455
27191
|
}
|
|
26456
27192
|
};
|
|
26457
|
-
/**
|
|
26458
|
-
|
|
26459
|
-
|
|
27193
|
+
/**
|
|
27194
|
+
* 流式中途刷新的节流间隔与上限。
|
|
27195
|
+
*
|
|
27196
|
+
* 企业微信单会话限制 30 条/分钟(含占位、收尾和卡片)。2500ms 约 24 次
|
|
27197
|
+
* 刷新/分钟,再加最多 3 条生命周期消息仍留有余量。
|
|
27198
|
+
*/
|
|
27199
|
+
_MessageRouter.STREAM_CHUNK_INTERVAL_MS = 2500;
|
|
27200
|
+
_MessageRouter.MAX_STREAM_CHUNKS = 190;
|
|
27201
|
+
/** 流式回复的硬超时:企微要求 10 分钟内 `finish=true`,留 2 分钟余量。 */
|
|
27202
|
+
_MessageRouter.STREAM_TIMEOUT_MS = 8 * 6e4;
|
|
27203
|
+
/**
|
|
27204
|
+
* `MESSAGE_COMPLETED` 后等待 `reply:ready` 的宽限。
|
|
27205
|
+
*
|
|
27206
|
+
* 正常完成通常由 `reply:ready` 用权威全文收尾;但最终 AI 消息为空时
|
|
27207
|
+
* (例如模型只调用了 `wecom_card`、没有产出文字)不会触发 `reply:ready`,
|
|
27208
|
+
* 这里兜底自行收尾,避免流挂住、卡片丢失。
|
|
27209
|
+
*/
|
|
27210
|
+
_MessageRouter.COMPLETION_GRACE_MS = 3e3;
|
|
26460
27211
|
var MessageRouter = _MessageRouter;
|
|
26461
27212
|
|
|
26462
27213
|
// src/channels/room/RoomChannelAdapter.ts
|
|
26463
|
-
import { z as
|
|
27214
|
+
import { z as z14 } from "zod";
|
|
26464
27215
|
import { snapshotExactRecord as snapshotExactRecord16 } from "@axiom-lattice/protocols";
|
|
26465
|
-
var roomConfigSchema =
|
|
27216
|
+
var roomConfigSchema = z14.object({}).strict();
|
|
26466
27217
|
function createRoomChannelAdapter(writer) {
|
|
26467
27218
|
return {
|
|
26468
27219
|
channel: "room",
|
|
@@ -26624,7 +27375,7 @@ var PluginDeploymentError = class extends Error {
|
|
|
26624
27375
|
this.code = code;
|
|
26625
27376
|
}
|
|
26626
27377
|
};
|
|
26627
|
-
function
|
|
27378
|
+
function isRecord5(value) {
|
|
26628
27379
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
26629
27380
|
}
|
|
26630
27381
|
function buildBootstrapEntrypoint(pluginType) {
|
|
@@ -27178,7 +27929,7 @@ var PluginDeploymentService = class {
|
|
|
27178
27929
|
return { ok: false, error: `health responded with HTTP ${response.status}` };
|
|
27179
27930
|
}
|
|
27180
27931
|
const body = await response.json().catch(() => void 0);
|
|
27181
|
-
if (
|
|
27932
|
+
if (isRecord5(body) && body.ok === true) {
|
|
27182
27933
|
return { ok: true };
|
|
27183
27934
|
}
|
|
27184
27935
|
return { ok: false, error: "health returned an invalid envelope" };
|
|
@@ -27825,6 +28576,7 @@ var start = async (config) => {
|
|
|
27825
28576
|
const adapterRegistry = new ChannelAdapterRegistry();
|
|
27826
28577
|
adapterRegistry.register(larkChannelAdapter);
|
|
27827
28578
|
adapterRegistry.register(wecomChannelAdapter);
|
|
28579
|
+
registerWecomCardTool();
|
|
27828
28580
|
const { wechatChannelAdapter } = await import("./WechatChannelAdapter-7WHE5UGY.mjs");
|
|
27829
28581
|
adapterRegistry.register(wechatChannelAdapter);
|
|
27830
28582
|
try {
|
|
@@ -28059,6 +28811,7 @@ var start = async (config) => {
|
|
|
28059
28811
|
taskLifecycleNotifier3.setOutbox(outboxStore);
|
|
28060
28812
|
projectTaskAdmissionNotifier2.setOutbox(outboxStore);
|
|
28061
28813
|
configurePluginCatalogSync({ outbox: outboxStore, owner: getReplicaId7() });
|
|
28814
|
+
configureCollectionSync({ outbox: outboxStore, owner: getReplicaId7() });
|
|
28062
28815
|
bridgeAssistantEvents(
|
|
28063
28816
|
(event, cb) => eventBus4.subscribe(event, cb),
|
|
28064
28817
|
outboxStore,
|
|
@@ -28089,6 +28842,12 @@ var start = async (config) => {
|
|
|
28089
28842
|
manager: mcpManager3,
|
|
28090
28843
|
convertToConnection: convertToConnection2
|
|
28091
28844
|
}),
|
|
28845
|
+
[COLLECTION_CHANGED_TOPIC]: async (payload) => {
|
|
28846
|
+
const tenantId = payload?.tenantId;
|
|
28847
|
+
if (typeof tenantId === "string" && tenantId.length > 0) {
|
|
28848
|
+
vectorStoreLatticeManager2.clearTenantVectorStores(tenantId);
|
|
28849
|
+
}
|
|
28850
|
+
},
|
|
28092
28851
|
[PLUGIN_CATALOG_CHANGED_TOPIC]: async (payload) => {
|
|
28093
28852
|
const tenantId = payload?.tenantId;
|
|
28094
28853
|
if (typeof tenantId === "string" && tenantId.length > 0) {
|