@axiom-lattice/protocols 4.8.0 → 4.10.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axiom-lattice/protocols",
3
- "version": "4.8.0",
3
+ "version": "4.10.0",
4
4
  "description": "Unified protocol type definitions for Axiom Lattice framework",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -43,6 +43,19 @@ export interface OutboundMessage {
43
43
  metadata?: Record<string, unknown>;
44
44
  }
45
45
 
46
+ /**
47
+ * 流式回复的一个片段。
48
+ *
49
+ * `text` 是**累计全文**(不是增量)—— 与微信 `stream.content` 的语义一致:
50
+ * 每次推送都是"把当前消息刷新成这段内容"。实现方据此避免各自维护拼接状态。
51
+ */
52
+ export interface OutboundStreamChunk {
53
+ /** 截至目前要展示的**累计**全文。 */
54
+ text: string;
55
+ /** 是否结束(最后一次刷新必须为 `true`)。 */
56
+ done: boolean;
57
+ }
58
+
46
59
  export interface DispatchResult {
47
60
  success: boolean;
48
61
  bindingId?: string;
@@ -76,12 +89,39 @@ export interface ChannelAdapter<TConfig = unknown> {
76
89
  message: OutboundMessage,
77
90
  installation: ChannelInstallation,
78
91
  ): Promise<void>;
92
+ /**
93
+ * 可选:流式回复。
94
+ *
95
+ * 实现该方法后,`MessageRouter` 会在等待 `reply:ready` 的同时,把 agent
96
+ * 的**增量输出**按节流推给对方;`reply:ready` 到达时以 `done: true` 收尾。
97
+ *
98
+ * 实现约定:
99
+ * - 同一次入站消息的多次刷新必须关联到**同一条**消息(微信即同一个
100
+ * `stream.id`,`req_id` 取 `replyTarget.rawTarget.frame`);
101
+ * - 必须保证最终以 `done: true` 收尾(调用方保证至少会调一次);
102
+ * - 失败不应抛出到派发链路(吞掉并记日志)。
103
+ *
104
+ * 未实现时行为与之前完全一致(只在 `reply:ready` 时调一次 `sendReply`)。
105
+ */
106
+ sendStreamChunk?(
107
+ replyTarget: ReplyTarget,
108
+ chunk: OutboundStreamChunk,
109
+ installation: ChannelInstallation,
110
+ ): Promise<void>;
79
111
  /**
80
112
  * 可选:把 Agent 暂停等待人工输入时的问题/提示投影到会话中。
81
113
  *
82
114
  * 与 `sendReply` 不同,这不是对某条入站消息的最终回复,而是运行中途
83
115
  * 产生的一条独立 bot 消息(例如 Project Room 的 clarify 卡片)。
84
- * `message.metadata.inputMessageId` 关联触发该运行的入站队列消息。
116
+ *
117
+ * 支持两种(互斥的)关联方式,均由 `message.metadata` 承载:
118
+ * - `message.metadata.inputMessageId`:legacy presence 关联,指向触发该
119
+ * 运行的入站队列消息;需配合 `replyTarget.rawTarget` 中的
120
+ * `sourceRoomMessageId` / `inputMessageId`。
121
+ * - `message.metadata.correlationKey`(可选):**source-less/correlated**
122
+ * 中断,没有入站输入消息;由调用方提供关联键,幂等性以「每次中断」为单位
123
+ * (idempotency key 派生自该 correlationKey),`replyTarget.rawTarget`
124
+ * 只需 `targetMembershipId`。
85
125
  */
86
126
  sendInterrupt?(
87
127
  replyTarget: ReplyTarget,
@@ -1,5 +1,5 @@
1
1
  /** Channel types persisted by the installation store, including internal-only channels. */
2
- export type ChannelInstallationType = "lark" | "email" | "slack" | "wechat" | "room";
2
+ export type ChannelInstallationType = "lark" | "email" | "slack" | "wechat" | "wecom" | "room";
3
3
 
4
4
  /** Channel types exposed by the public installation management API. */
5
5
  export type PublicChannelInstallationType = Exclude<ChannelInstallationType, "room">;
@@ -19,6 +19,19 @@ export interface WechatChannelInstallationConfig {
19
19
  uin?: string;
20
20
  }
21
21
 
22
+ /**
23
+ * Credentials for a WeCom (企业微信) 智能机器人 installation using the
24
+ * long-connection (API 模式) transport. Only `botId` + `secret` are needed —
25
+ * no CorpID / AgentID / Token / EncodingAESKey, and no callback URL.
26
+ */
27
+ export interface WecomChannelInstallationConfig {
28
+ /** 机器人 BotID. */
29
+ botId: string;
30
+ /** 长连接专用 Secret. */
31
+ secret: string;
32
+ assistantId?: string;
33
+ }
34
+
22
35
  /** A persisted tenant-scoped channel installation. */
23
36
  export interface ChannelInstallation<TConfig = unknown> {
24
37
  id: string;
@@ -49,6 +62,7 @@ type PublicInstallationBase = Omit<CreateChannelInstallationInput, "channel" | "
49
62
  export type CreateChannelInstallationRequest = PublicInstallationBase & (
50
63
  | { channel: "lark"; config: LarkChannelInstallationConfig }
51
64
  | { channel: "wechat"; config: WechatChannelInstallationConfig }
65
+ | { channel: "wecom"; config: WecomChannelInstallationConfig }
52
66
  | { channel: "email" | "slack"; config: Record<string, unknown> }
53
67
  );
54
68
 
@@ -24,6 +24,17 @@ export interface LLMConfig {
24
24
  streaming?: boolean;
25
25
  apiKeyEnvName?: string;
26
26
  apiKey?: string;
27
+ /**
28
+ * The upstream requires no credentials.
29
+ *
30
+ * A locally hosted model server — Ollama, vLLM, LM Studio — usually accepts
31
+ * unauthenticated requests. Without this flag a config carrying no key falls
32
+ * back to `process.env[apiKeyEnvName ?? "<PROVIDER>_API_KEY"]`, which would
33
+ * send the *gateway's own* deployment credential to whatever host the tenant
34
+ * configured. Setting it makes "no credential" an explicit statement rather
35
+ * than a missing value, so the fallback never runs.
36
+ */
37
+ noAuth?: boolean;
27
38
  baseURL?: string;
28
39
  modelKwargs?: Record<string, any>;
29
40
  extra?: Record<string, any>;
@@ -237,14 +237,16 @@ export interface CreateModelProviderRequest {
237
237
  baseURL: string;
238
238
 
239
239
  /**
240
- * Upstream API key. Required: a provider must always carry a credential.
240
+ * Upstream API key. Optional: omitted means the upstream needs no
241
+ * authentication, which is the normal case for a locally hosted model server
242
+ * (Ollama, vLLM, LM Studio).
241
243
  *
242
- * A keyless provider is not merely useless — it is unsafe. `initChatModel`
243
- * falls back to `process.env[apiKeyEnvName ?? "OPENAI_API_KEY"]` when the
244
- * config carries no key, so a keyless provider pointed at a tenant-chosen
245
- * host would send the gateway's own deployment credential to it.
244
+ * A keyless provider must never fall back to the gateway's own environment
245
+ * credentials. `initChatModel` would otherwise reach for `OPENAI_API_KEY` and
246
+ * send the deployment's credential to the tenant's host. The runtime config
247
+ * carries an explicit no-auth marker instead — see `LLMConfig.noAuth`.
246
248
  */
247
- apiKey: string;
249
+ apiKey?: string;
248
250
 
249
251
  /**
250
252
  * Whether this provider participates in discovery and registration
@@ -350,6 +352,21 @@ export interface ProviderModelInput extends DiscoveredModel {
350
352
  * Distinct from `enabled: false`, which means the tenant switched it off.
351
353
  */
352
354
  stale?: boolean;
355
+ /**
356
+ * Per-model LLM config, merged over the provider's own settings when the
357
+ * runtime client is built.
358
+ *
359
+ * This is what lets one model on a provider run at a different temperature,
360
+ * or carry `modelKwargs` the server needs, without duplicating the provider.
361
+ */
362
+ extra?: Record<string, unknown>;
363
+ /**
364
+ * Whether this is the tenant's default model.
365
+ *
366
+ * Omitted means "leave the stored flag alone": a discovery pass carries no
367
+ * opinion about which model is the default, and must not clear it.
368
+ */
369
+ isDefault?: boolean;
353
370
  }
354
371
 
355
372
  /**
@@ -365,6 +382,22 @@ export interface ModelSelection {
365
382
  enabled: boolean;
366
383
  /** The upstream no longer offers this model; the row is kept, flagged. */
367
384
  stale?: boolean;
385
+ /**
386
+ * Per-model LLM config, merged over the provider's own settings.
387
+ *
388
+ * Omitted means "leave whatever is stored alone" — the selection carries only
389
+ * what the caller is changing, and a model edited through the picker must not
390
+ * lose the config it already had.
391
+ */
392
+ extra?: Record<string, unknown>;
393
+ /**
394
+ * Whether this is the tenant's default model.
395
+ *
396
+ * At most one model per tenant may carry it. Omitted means "leave the stored
397
+ * flag alone"; a default can only be replaced, never dropped by omission —
398
+ * see `ModelProviderStore.getDefaultModel`.
399
+ */
400
+ isDefault?: boolean;
368
401
  }
369
402
 
370
403
  /**
@@ -418,6 +451,22 @@ export interface ModelProviderModelEntry {
418
451
  */
419
452
  stale?: boolean;
420
453
 
454
+ /**
455
+ * Per-model LLM config, merged over the provider's own settings when the
456
+ * runtime client is built. Absent when the model runs on the provider's
457
+ * settings alone.
458
+ */
459
+ extra?: Record<string, unknown>;
460
+
461
+ /**
462
+ * Whether this is the tenant's default model.
463
+ *
464
+ * At most one row per tenant carries it, enforced by a partial unique index.
465
+ * It is what a bare `"default"` model key resolves to for that tenant, and it
466
+ * cannot be deleted — replacing it means marking another model first.
467
+ */
468
+ isDefault?: boolean;
469
+
421
470
  /**
422
471
  * Timestamp of the discovery that produced this row
423
472
  */
@@ -563,6 +612,19 @@ export interface TenantModelPublicEntry {
563
612
  */
564
613
  stale: boolean;
565
614
 
615
+ /**
616
+ * Per-model LLM config, merged over the provider's own settings. Absent when
617
+ * the model runs on the provider's settings alone.
618
+ */
619
+ extra?: Record<string, unknown>;
620
+
621
+ /**
622
+ * Whether this is the tenant's default model — the one a bare `"default"`
623
+ * key resolves to, and the one a tenant without its own would otherwise take
624
+ * from the deployment.
625
+ */
626
+ isDefault?: boolean;
627
+
566
628
  /**
567
629
  * Whether this model can actually be executed by an agent.
568
630
  * Phase 1: true only for the `default` tenant.