@weiliang0121/capix-model-exchanges 0.1.0 → 0.3.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/README.md CHANGED
@@ -1,37 +1,90 @@
1
1
  # ModelExchanges
2
2
 
3
- `@weiliang0121/capix-model-exchanges` 保存模型的完整回复与每个工具调用的正式反馈。它不保存调用运行状态,不执行工具,不把工具消息写入 Transcript。
3
+ `@weiliang0121/capix-model-exchanges` 保存一次模型对话的固定起点,以及从该起点累积的完整回复和工具反馈。起点包括初始消息、请求选项和模型可见的工具目录;Node、执行函数及资源权限仍留在结构与资源中。
4
4
 
5
5
  ```ts
6
6
  const exchanges = new ModelExchanges({ persistence });
7
- const resources = new Resources().register(new Resource({ exchanges, scope: 'business-loop-id' }), ModelExchangeResource);
8
- ```
7
+ const resources = new Resources().register(new Resource(exchanges), ModelExchangeResource);
8
+
9
+ // ID 由宿主或业务固定。需要恢复时,先在 prepared CP 保存 ID,再 begin。
10
+ const origin = exchanges.begin('prepared-origin-id', {
11
+ request: {
12
+ messages: [{ role: 'user', content: '查询天气' }],
13
+ maxTokens: 1024,
14
+ },
15
+ catalog: {
16
+ tools: [{ name: 'weather', description: '查询天气', parameters: { type: 'object' } }],
17
+ digest: 'weather-catalog-v1',
18
+ policyVersion: 'weather-policy-v1',
19
+ },
20
+ });
9
21
 
10
- 业务节点申请 `ModelExchangeResource.request()`。模型子结果的 `result.succeeded` 中调用 `intake(reply)`,工具结果接纳中调用 `receive(ref, callId, result)`;资源适配从当前 `activity.reception()` 取得真实父子来源。预检拒绝使用 `complete(ref, callId, result)`,不伪造子调用。宿主可直接使用实体方法并负责提供可信来源。
22
+ // 模型子调用只传引用;发送资源在自己的访问窗口内展开请求。
23
+ const input = { origin, exchanges: [] };
24
+ const request = exchanges.request(input);
25
+ // request 包含固定初始消息、选项、工具目录及所选交换前缀。
26
+ ```
11
27
 
12
- `scope` 只是宿主固定的业务归属,可以跨多次调用。它不是权限凭据,随机 UUID 也不提供授权;资源供给和宿主隔离决定访问范围。
28
+ 不需要为了提供初始材料,在父结构上额外安装一次 capture。一个集合可容纳多个独立起点,资源只需普通登记。`origin` 引用是内容身份,不是授权凭据;登记整个实体会开放该资源端点的集合范围,宿主隔离与资源供给仍负责访问范围。
13
29
 
14
30
  对象关系:
15
31
 
16
32
  ```text
17
33
  ModelExchanges
18
- └─ ModelExchangeEntry(创建顺序不变)
19
- ├─ 一份 ModelReply:共同输出 + 不重叠的原生补充
20
- ├─ feedback:按 toolCallId 固定的结果槽位
21
- └─ operation / receptions:固定接纳凭据
22
- ↓
23
- ModelExchangeResource ModelExchangeViewSource
24
- 受窗口控制的操作 同一交换作为一个 View 成员
34
+ ├─ origin A:初始请求 + 模型可见目录(固定)
35
+ │ ├─ exchange 1:完整模型回复 + 按 toolCallId 固定的反馈
36
+ │ └─ exchange 2:完整模型回复 + 按 toolCallId 固定的反馈
37
+ └─ origin B:另一份固定起点及其交换
38
+ ↓
39
+ ModelExchangeResource ModelExchangeViewSource
40
+ 本次窗口内的读写和请求重建 交换作为 View 成员,可限定一个 origin
41
+ ```
42
+
43
+ ## 起点与请求
44
+
45
+ - `begin(id, basis)` 固定整个纯数据输入。同 ID、同内容只确认,不新增提交或变化;不同内容明确拒绝。没有回复的空起点也进入持久序列。`origin(ref)` 核对集合、ID 和完整起点摘要后返回固定内容。
46
+ - `basis.request` 支持 ModelRequest 的消息与选项,不接受 `tools` 或 `exchanges`;目录统一放在 `basis.catalog`,历史由实体组织。选项保持原样,不隐含默认覆盖。空 `catalog.tools` 表达无工具模型调用,重建请求省略 `tools` 字段。
47
+ - `catalog.digest` 和 `policyVersion` 由执行结构提供兼容依据;实体另外计算 `origin.digest`,覆盖完整初始请求和目录。目录只包含模型可见的协议数据,不能放入 Node、映射回调或端点。恢复时,执行方仍须核对当前结构与保存目录是否兼容,实体不会从函数推导执行语义。
48
+ - `closed(origin, refs)` 要求引用属于同一起点、revision 准确、顺序为该起点最前面的 K 个交换,且每个工具反馈槽完整。允许选择较短的历史前缀以重建原请求;不能重排、跳过中间项或重复同一交换。它只确认反馈完整性,不判断当前业务能否继续。
49
+ - `request({ origin, exchanges })` 组合起点与明确前缀,执行 Provider 的完整请求校验,包括终止原因的续接资格和整体请求限额。它不发送请求,也不授权重试;查询旧前缀不等于恢复资格成立。实体只保存接纳顺序,模型子输入与实际发送历史的因果一致性仍由调用和宿主核验。
50
+
51
+ ## 接纳与反馈
52
+
53
+ 节点申请 `ModelExchangeResource.request()`。接纳模型子结果时调用 `work.resources.exchanges.intake(origin, reply)`,接纳工具子结果时调用 `receive(origin, ref, callId, result)`。资源适配从当前 `activity.reception()` 取得准确父子来源;预检拒绝使用 `complete(origin, ref, callId, result)`,不伪造子调用。直接调用实体方法的宿主负责提供可信来源。
54
+
55
+ - 实体 `intake(origin, operation, reply)` 对同一固定操作确认原回复,来源或内容冲突拒绝。工具的原始名称、调用 ID 和参数保存在完整回复中,不因反馈写入而丢失。
56
+ - 实体 `complete(origin, ref, callId, result, operation?)` 新增反馈要求准确集合、起点、交换 ID 和当前 revision。已有槽位同内容、同来源的确认允许较旧引用,返回当前引用,不新增 sequence。
57
+ - `read(origin, ref)` 默认要求准确 revision;需要查询最新槽位以确认固定效果时,显式传 `false`。`values(origin?)` 返回创建顺序中的交换;不传起点则读取全体。
58
+ - 接纳回执保留准确引用、来源及反馈摘要。它们是确认依据,不构成工具效果、接纳与 CP 的统一事务。
59
+
60
+ ## 持久化与恢复
61
+
62
+ 当前快照是 **formatVersion 2**,包含 `origins`、`entries` 和共同变化序列。旧格式、旧 `scope` 供给形状明确拒绝,不提供迁移或同义兼容 API。
63
+
64
+ 持久端口同步接收两种提交:
65
+
66
+ ```ts
67
+ commit(change) {
68
+ // 在同一事务中比较 collection 和 expectedSequence。
69
+ if (change.kind === 'origin') {
70
+ // 首次保存 change.origin。
71
+ } else {
72
+ // 核对 change.previous,再保存 change.entry。
73
+ }
74
+ // 同事务推进 sequence;成功返回后实体才安装并发布变化。
75
+ }
25
76
  ```
26
77
 
27
- - `intake(scope, operation, reply)` 对同一固定操作确认原回复;冲突拒绝。操作键和反馈摘要采用规范化 JSON,不依赖对象属性构造顺序。
28
- - `complete` 新增反馈要求准确集合、scope、交换 ID 和当前 revision;已有槽位的同内容、同来源确认允许较旧引用,不新增 sequence。确认返回当前引用。
29
- - `read` 默认要求准确 revision;资源 `read` 明确允许旧版本引用查询当前槽位,供固定接纳确认。`closed` 要求准确引用、没有重复交换,并且每个工具槽位完整。终止原因是否允许续接仍由模型/业务契约解释。
30
- - `snapshot/restore` 接纳并冻结纯 JSON。恢复先核验集合外壳与条目数组,再逐条捕获、核验并累计集合容量;不将单条目的结构节点预算误用于整个集合。检查版本、创建顺序、变化计数、唯一效果来源、工具槽位与来源关联,不发布变化或调用持久端口。
31
- - 可选持久端口先同步 `commit({ collection, expectedSequence, sequence, previous, entry })`,成功后才安装并发布变化。宿主应原子核验 sequence 和 previous。端口失败或异步返回产生 `PersistenceError`,该实体停止后续写入;宿主重新载入决定未知提交的实际结果。不会自动重试。变化发布失败保留已经成立的事实。
32
- - 默认预算 `defaultModelExchangeLimits` 为 10,000 个交换、累计 64 MiB 的交换 JSON;可显式调整,`maximumModelExchangeLimits` 限定最大 100,000 个、128 MiB。每个交换最大 `maximumModelExchangeEntryBytes`(8 MiB),Provider 单回复/单反馈自己的限制仍适用。超限在持久提交前拒绝,不将实体标为持久故障;已有事实仍可读取和确认。没有自动淘汰:过期与保留策略归宿主。
33
- - ViewSource 可以固定某个 scope 或全体;版本覆盖成员和反馈变化,完整目录按创建顺序返回,`inspect/read` 只访问请求的 keys。旧快照保持原值,版本变化后读取拒绝。视图策略可退出整个交换,不能留下孤立 tool 消息。
78
+ 端口失败或异步返回产生 `PersistenceError`,实体停止后续写入;宿主重新载入确认未知提交的实际结果,不自动重试。变化发布失败保留已经成立的事实。同内容确认不重复提交。
79
+
80
+ `ModelExchanges.restore(state, { persistence, limits })` 核验起点摘要、交换归属、创建顺序、变化计数、唯一效果来源、槽位和来源关联,再安装固定内容;不发布变化或调用持久端口。丢失起点时不能仅凭交换补造初始消息或目录。持久适配应保真保存已接纳数据,例如 `-0` 经普通 JSON 往返变成 `0` 会导致摘要拒绝,不能当作同一份依据。
81
+
82
+ 默认预算为 10,000 个起点、10,000 个交换、累计 64 MiB 的起点与交换 JSON 正文;最大分别为 100,000、100,000 和 128 MiB。单起点、单交换均至多 8 MiB;Provider 对初始及重建请求另有限额(当前 2 MiB),不因集合容量较大而放宽。预算不等于堆内存上界。
83
+
84
+ 超限在持久提交前拒绝,不将实体标为持久故障;已有事实仍可读取和确认。预算不写入快照,恢复应传入相同配置;更低预算不足时拒绝,不截断。没有自动淘汰、删除或跨实体事务;宿主负责被恢复依据引用的内容保留。
85
+
86
+ ## View 与验证范围
34
87
 
35
- 宿主负责集合的创建、持久化和重新载入。当前快照通过 `ModelExchanges.restore(state, { persistence, limits })` 恢复;旧格式明确拒绝,不自动迁移。预算不写进快照,恢复时应传入相同配置;更低预算无法容纳快照时明确拒绝,不截断或淘汰事实。
88
+ `new ModelExchangeViewSource(exchanges, origin?)` 可读取全体或一个准确起点的交换。起点本身不是自动加入 View 的材料,内容选择仍由策略决定。版本覆盖起点建立、成员与反馈变化;`inspect/read` 只读取请求的 keys,旧快照保持原值,来源版本变化后读取拒绝。退出视图不删除事实。
36
89
 
37
- 实体提交不与 Invocation CP 或其他实体构成事务。退出 View 或关闭宿主持有的句柄不会删除交换事实,宿主仍需保留被恢复或续接依据引用的集合。参考服务的 SQLite 装配与归档入口规则见仓库中的 Conversation 服务文档;它们不属于本包的依赖或存储契约。
90
+ 包内测试覆盖起点固定、请求重建、预算、持久未知、格式拒绝和 View;Runtime 包的集成测试使用真实 Scene/Node/Group、资源、Interaction 与 CP,验证普通登记和新 Runner 恢复,核对缺失或变化的依据不会触发重发。[Tool](../tool/README.md) 提供公开 Group 预设;不把离线恢复验证当作外部工具效果的恢复承诺。
@@ -1,11 +1,12 @@
1
1
  import { type ChangeCapture, type EventConsumer } from '@weiliang0121/capix-changes';
2
- import { type ModelReply, type ToolResult } from '@weiliang0121/capix-model-provider';
3
- import type { ExchangeOperation, ExchangeReference, ModelExchangeChange, ModelExchangeEntry, ModelExchangesPersistence, ModelExchangesState } from './types.js';
2
+ import { type ModelReply, type ModelRequest, type ToolResult } from '@weiliang0121/capix-model-provider';
3
+ import type { ExchangeOperation, ExchangeReference, ExchangeOriginReference, ExchangePrefix, ModelExchangeBasis, ModelExchangeOrigin, ModelExchangeChange, ModelExchangeEntry, ModelExchangesPersistence, ModelExchangesState } from './types.js';
4
4
  export interface ModelExchangesOptions {
5
5
  readonly persistence?: ModelExchangesPersistence;
6
6
  readonly changes?: ChangeCapture<ModelExchangeChange>;
7
7
  readonly observation?: EventConsumer<ModelExchangeChange>;
8
8
  readonly limits?: {
9
+ readonly origins?: number;
9
10
  readonly entries?: number;
10
11
  readonly bytes?: number;
11
12
  };
@@ -15,11 +16,15 @@ export declare class ModelExchanges {
15
16
  constructor(options?: ModelExchangesOptions);
16
17
  get id(): string;
17
18
  get sequence(): number;
18
- intake(scope: string, operation: ExchangeOperation, reply: ModelReply): ExchangeReference;
19
- complete(scope: string, reference: ExchangeReference, toolCallId: string, result: ToolResult, operation?: ExchangeOperation): ExchangeReference;
20
- read(scope: string, value: ExchangeReference, exact?: boolean): ModelExchangeEntry;
21
- closed(scope: string, references: readonly ExchangeReference[]): readonly ModelExchangeEntry[];
22
- values(scope?: string): readonly ModelExchangeEntry[];
19
+ begin(id: string, basis: ModelExchangeBasis): ExchangeOriginReference;
20
+ origin(value: ExchangeOriginReference): ModelExchangeOrigin;
21
+ private originReference;
22
+ intake(originReference: ExchangeOriginReference, operation: ExchangeOperation, reply: ModelReply): ExchangeReference;
23
+ complete(originReference: ExchangeOriginReference, reference: ExchangeReference, toolCallId: string, result: ToolResult, operation?: ExchangeOperation): ExchangeReference;
24
+ read(originReference: ExchangeOriginReference, value: ExchangeReference, exact?: boolean): ModelExchangeEntry;
25
+ closed(originReference: ExchangeOriginReference, references: readonly ExchangeReference[]): readonly ModelExchangeEntry[];
26
+ request(value: ExchangePrefix): ModelRequest;
27
+ values(originReference?: ExchangeOriginReference): readonly ModelExchangeEntry[];
23
28
  get(id: string): ModelExchangeEntry | undefined;
24
29
  reference(entry: ModelExchangeEntry): ExchangeReference;
25
30
  snapshot(): ModelExchangesState;
@@ -27,5 +32,6 @@ export declare class ModelExchanges {
27
32
  private mutate;
28
33
  private nextSequence;
29
34
  private install;
35
+ private persist;
30
36
  }
31
37
  export declare function requireModelExchangeEntry(value: unknown): ModelExchangeEntry;
package/dist/exchanges.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /*! MIT License | Copyright (c) 2026 wei.liang | See LICENSE */
2
- var __defProp=Object.defineProperty;var __name=(target,value)=>__defProp(target,"name",{value,configurable:!0});import{randomUUID}from"node:crypto";import{isDeepStrictEqual}from"node:util";import{RuntimeError}from"@weiliang0121/capix-contracts";import{PersistenceError}from"@weiliang0121/capix-diagnostics";import{publishFact}from"@weiliang0121/capix-changes";import{requireModelReply,requireToolFeedback,requireToolResult}from"@weiliang0121/capix-model-provider";import{canonical,capture,captureSnapshotEnvelope,counter,encodedBytes,identity,record,requireExchangeOperation,requireExchangeReference}from"./value.js";import{defaultModelExchangeLimits,maximumModelExchangeLimits,maximumModelExchangeEntryBytes}from"./limits.js";class ModelExchanges{static{__name(this,"ModelExchanges")}#id=randomUUID();#sequence=0;#bytes=0;#mutating=!1;#persistenceFailure;#entries=new Map;#operations=new Map;#entryLimit;#byteLimit;#persistence;#changes;#observation;constructor(options={}){this.#entryLimit=limit(options.limits?.entries??defaultModelExchangeLimits.entries,maximumModelExchangeLimits.entries,"entries"),this.#byteLimit=limit(options.limits?.bytes??defaultModelExchangeLimits.bytes,maximumModelExchangeLimits.bytes,"bytes"),this.#persistence=options.persistence,this.#changes=options.changes,this.#observation=options.observation}get id(){return this.#id}get sequence(){return this.#sequence}intake(scope,operation,reply){return this.mutate(()=>{scope=identity(scope),operation=requireExchangeOperation(operation),reply=requireModelReply(capture(reply));const key=canonical(operation),previous=this.#operations.get(key);if(previous){const entry2=this.#entries.get(previous.id);if(previous.callId!==void 0||entry2.scope!==scope||!isDeepStrictEqual(entry2.reply,reply))throw conflict("Intake differs from its fixed operation");return this.reference(entry2)}const sequence=this.nextSequence(),entry=Object.freeze({id:randomUUID(),scope,revision:0,sequence,operation,reply,feedback:Object.freeze([]),receptions:Object.freeze([])});return this.install(entry,null,sequence,{key,id:entry.id}),this.reference(entry)})}complete(scope,reference,toolCallId,result,operation){return this.mutate(()=>{const previous=this.read(scope,reference,!1);if(toolCallId=identity(toolCallId),result=requireToolResult(capture(result)),operation=operation===void 0?void 0:requireExchangeOperation(operation),!previous.reply.output.some(item=>item.kind==="tool-call"&&item.id===toolCallId))throw conflict("Unknown tool feedback slot");const feedback=requireToolFeedback(capture({toolCallId,...result})),existing=previous.feedback.find(item=>item.toolCallId===toolCallId);if(existing){const original=previous.receptions.find(item=>item.callId===toolCallId)?.operation;if(!isDeepStrictEqual(existing,feedback)||!isDeepStrictEqual(original,operation))throw conflict("Feedback differs from its fixed slot");return this.reference(previous)}this.read(scope,reference);const key=operation===void 0?void 0:canonical(operation);if(key&&this.#operations.has(key))throw conflict("Feedback operation already belongs to another effect");const sequence=this.nextSequence(),next=Object.freeze({...previous,revision:previous.revision+1,feedback:Object.freeze([...previous.feedback,feedback]),receptions:operation?Object.freeze([...previous.receptions,Object.freeze({callId:toolCallId,operation})]):previous.receptions});return this.install(next,previous,sequence,key?{key,id:next.id,callId:toolCallId}:void 0,toolCallId),this.reference(next)})}read(scope,value,exact=!0){scope=identity(scope);const reference=requireExchangeReference(value),entry=this.#entries.get(reference.id);if(reference.collection!==this.id||!entry||entry.scope!==scope||reference.revision>entry.revision||exact&&reference.revision!==entry.revision)throw conflict("Exchange reference scope or revision mismatch");return entry}closed(scope,references){if(!Array.isArray(references)||references.length>this.#entryLimit)throw new TypeError("Invalid exchange reference list");const accepted=capture(references);if(!Array.isArray(accepted))throw new TypeError("Invalid exchange reference list");const seen=new Set;return Object.freeze(accepted.map(reference=>{const entry=this.read(scope,requireExchangeReference(reference));if(seen.has(entry.id))throw conflict("Duplicate exchange reference");if(seen.add(entry.id),entry.reply.output.filter(item=>item.kind==="tool-call").length!==entry.feedback.length)throw conflict("Exchange is missing tool feedback");return entry}))}values(scope){return scope!==void 0&&identity(scope),Object.freeze([...this.#entries.values()].filter(entry=>scope===void 0||entry.scope===scope))}get(id){return this.#entries.get(identity(id))}reference(entry){if(this.#entries.get(entry.id)!==entry)throw conflict("Exchange entry is not the current entity value");return Object.freeze({collection:this.id,id:entry.id,revision:entry.revision})}snapshot(){return Object.freeze({formatVersion:1,id:this.id,sequence:this.sequence,entries:this.values()})}static restore(value,options={}){const entity=new ModelExchanges(options),saved=captureSnapshotEnvelope(value,entity.#entryLimit);if(saved.formatVersion!==1)throw new TypeError("Unsupported model exchange snapshot");entity.#id=identity(saved.id),entity.#sequence=counter(saved.sequence);let previousCreated=0,changes=0;for(const value2 of saved.entries){const entry=requireModelExchangeEntry(value2);if(entity.#entries.has(entry.id)||entry.sequence<=previousCreated||entry.sequence>changes+1||entry.sequence+entry.revision>entity.sequence)throw new TypeError("Invalid saved model exchange order");if(previousCreated=entry.sequence,changes+=1+entry.revision,entity.#bytes+=encodedBytes(entry),entity.#entries.size>=entity.#entryLimit||entity.#bytes>entity.#byteLimit)throw new RangeError("Model exchange snapshot exceeds collection limits");for(const[operation,callId]of[[entry.operation,void 0],...entry.receptions.map(item=>[item.operation,item.callId])]){const key=canonical(operation);if(entity.#operations.has(key))throw new TypeError("Duplicate saved model exchange operation");entity.#operations.set(key,{id:entry.id,...callId===void 0?{}:{callId}})}entity.#entries.set(entry.id,entry)}if(changes!==entity.sequence)throw new TypeError("Model exchange sequence does not match its changes");return entity}mutate(action){if(this.#persistenceFailure)throw this.#persistenceFailure;if(this.#mutating)throw new Error("Model exchange persistence or publication cannot reenter mutation");this.#mutating=!0;try{return action()}finally{this.#mutating=!1}}nextSequence(){if(!Number.isSafeInteger(this.#sequence+1))throw new RangeError("Model exchange sequence is exhausted");return this.#sequence+1}install(entry,previous,sequence,operation,toolCallId){const size=encodedBytes(entry),bytes=this.#bytes+size-(previous?encodedBytes(previous):0);if(size>maximumModelExchangeEntryBytes||bytes>this.#byteLimit||!previous&&this.#entries.size>=this.#entryLimit)throw new RangeError("Model exchange collection exceeds capacity");const change=Object.freeze({collection:this.id,expectedSequence:this.sequence,sequence,previous,entry});try{const result=this.#persistence?.commit(change);if(result!==void 0)throw Promise.resolve(result).catch(()=>{}),new TypeError("Model exchange persistence must return undefined synchronously")}catch(cause){throw this.#persistenceFailure=cause instanceof PersistenceError?cause:new PersistenceError("model-exchanges.commit",cause),this.#persistenceFailure}this.#entries.set(entry.id,entry),this.#bytes=bytes,this.#sequence=sequence,operation&&this.#operations.set(operation.key,{id:operation.id,...operation.callId===void 0?{}:{callId:operation.callId}});const event=Object.freeze({type:"model-exchange",collection:this.id,sequence,reference:this.reference(entry),scope:entry.scope,kind:previous?"feedback":"intake",...toolCallId===void 0?{}:{toolCallId}});publishFact(event,this.#changes,this.#observation)}}function requireModelExchangeEntry(value){const item=record(capture(value),["id","scope","revision","sequence","operation","reply","feedback","receptions"]);if(!Array.isArray(item.feedback)||!Array.isArray(item.receptions))throw new TypeError("Invalid saved exchange feedback");const reply=requireModelReply(item.reply),feedback=Object.freeze(item.feedback.map(requireToolFeedback)),calls=new Set(reply.output.filter(entry=>entry.kind==="tool-call").map(entry=>entry.id)),completed=new Set(feedback.map(entry=>entry.toolCallId));if(completed.size!==feedback.length||feedback.some(entry=>!calls.has(entry.toolCallId))||item.revision!==feedback.length)throw new TypeError("Invalid saved exchange slots");const receptions=Object.freeze(item.receptions.map(value2=>{const entry=record(value2,["callId","operation"]),callId=identity(entry.callId);if(!completed.has(callId))throw new TypeError("Saved feedback operation has no corresponding slot");return Object.freeze({callId,operation:requireExchangeOperation(entry.operation)})}));if(new Set(receptions.map(item2=>item2.callId)).size!==receptions.length)throw new TypeError("Duplicate saved feedback operation");const sequence=counter(item.sequence);if(sequence===0)throw new TypeError("Exchange creation sequence must be positive");return Object.freeze({id:identity(item.id),scope:identity(item.scope),revision:counter(item.revision),sequence,operation:requireExchangeOperation(item.operation),reply,feedback,receptions})}__name(requireModelExchangeEntry,"requireModelExchangeEntry");function limit(value,maximum,name){if(!Number.isSafeInteger(value)||value<1||value>maximum)throw new RangeError(`Invalid model exchange ${name} limit`);return value}__name(limit,"limit");function conflict(message){return new RuntimeError("MODEL_EXCHANGE_CONFLICT",message)}__name(conflict,"conflict");export{ModelExchanges,requireModelExchangeEntry};
2
+ var __defProp=Object.defineProperty;var __name=(target,value)=>__defProp(target,"name",{value,configurable:!0});import{randomUUID}from"node:crypto";import{isDeepStrictEqual}from"node:util";import{isProxy}from"node:util/types";import{RuntimeError}from"@weiliang0121/capix-contracts";import{PersistenceError}from"@weiliang0121/capix-diagnostics";import{publishFact}from"@weiliang0121/capix-changes";import{requireModelReply,requireModelRequest,requireToolFeedback,requireToolResult}from"@weiliang0121/capix-model-provider";import{canonical,capture,captureSnapshotEnvelope,counter,encodedBytes,identity,record,requireExchangeOperation,requireExchangeReference,requireExchangeOriginReference}from"./value.js";import{defaultModelExchangeLimits,maximumModelExchangeLimits,maximumModelExchangeEntryBytes,maximumModelExchangeOriginBytes}from"./limits.js";import{requireModelExchangeBasis,requireModelExchangeOrigin,exchangeOriginDigest}from"./origin.js";class ModelExchanges{static{__name(this,"ModelExchanges")}#id=randomUUID();#sequence=0;#bytes=0;#mutating=!1;#persistenceFailure;#origins=new Map;#originEntries=new Map;#sizes=new Map;#originLimit;#entries=new Map;#operations=new Map;#entryLimit;#byteLimit;#persistence;#changes;#observation;constructor(options={}){this.#originLimit=limit(options.limits?.origins??defaultModelExchangeLimits.origins,maximumModelExchangeLimits.origins,"origins"),this.#entryLimit=limit(options.limits?.entries??defaultModelExchangeLimits.entries,maximumModelExchangeLimits.entries,"entries"),this.#byteLimit=limit(options.limits?.bytes??defaultModelExchangeLimits.bytes,maximumModelExchangeLimits.bytes,"bytes"),this.#persistence=options.persistence,this.#changes=options.changes,this.#observation=options.observation}get id(){return this.#id}get sequence(){return this.#sequence}begin(id,basis){return this.mutate(()=>{id=identity(id),basis=requireModelExchangeBasis(basis);const digest=exchangeOriginDigest(basis),previous=this.#origins.get(id);if(previous){if(previous.digest!==digest||!isDeepStrictEqual(previous.request,basis.request)||!isDeepStrictEqual(previous.catalog,basis.catalog))throw conflict("Origin differs from its fixed basis");return this.originReference(previous)}if(this.#entries.has(id))throw conflict("Origin identity already belongs to an exchange");const sequence=this.nextSequence(),origin=Object.freeze({id,sequence,digest,...basis}),size=encodedBytes(origin);if(size>maximumModelExchangeOriginBytes||this.#bytes+size>this.#byteLimit||this.#origins.size>=this.#originLimit)throw new RangeError("Model exchange collection exceeds capacity");this.persist(Object.freeze({kind:"origin",collection:this.id,expectedSequence:this.sequence,sequence,origin})),this.#origins.set(id,origin),this.#originEntries.set(id,[]),this.#bytes+=size,this.#sequence=sequence;const reference=this.originReference(origin);return publishFact(Object.freeze({type:"model-exchange",collection:this.id,sequence,origin:id,kind:"origin",reference}),this.#changes,this.#observation),reference})}origin(value){const reference=requireExchangeOriginReference(value),origin=this.#origins.get(reference.id);if(reference.collection!==this.id||!origin||origin.digest!==reference.digest)throw conflict("Exchange origin reference mismatch");return origin}originReference(origin){return Object.freeze({collection:this.id,id:origin.id,digest:origin.digest})}intake(originReference,operation,reply){return this.mutate(()=>{const origin=this.origin(originReference);operation=requireExchangeOperation(operation),reply=requireModelReply(capture(reply));const key=canonical(operation),previous=this.#operations.get(key);if(previous){const entry2=this.#entries.get(previous.id);if(previous.callId!==void 0||entry2.origin!==origin.id||!isDeepStrictEqual(entry2.reply,reply))throw conflict("Intake differs from its fixed operation");return this.reference(entry2)}const sequence=this.nextSequence(),entry=Object.freeze({id:randomUUID(),origin:origin.id,revision:0,sequence,operation,reply,feedback:Object.freeze([]),receptions:Object.freeze([])});return this.install(entry,null,sequence,{key,id:entry.id}),this.reference(entry)})}complete(originReference,reference,toolCallId,result,operation){return this.mutate(()=>{const previous=this.read(originReference,reference,!1);if(toolCallId=identity(toolCallId),result=requireToolResult(capture(result)),operation=operation===void 0?void 0:requireExchangeOperation(operation),!previous.reply.output.some(item=>item.kind==="tool-call"&&item.id===toolCallId))throw conflict("Unknown tool feedback slot");const feedback=requireToolFeedback(capture({toolCallId,...result})),existing=previous.feedback.find(item=>item.toolCallId===toolCallId);if(existing){const original=previous.receptions.find(item=>item.callId===toolCallId)?.operation;if(!isDeepStrictEqual(existing,feedback)||!isDeepStrictEqual(original,operation))throw conflict("Feedback differs from its fixed slot");return this.reference(previous)}this.read(originReference,reference);const key=operation===void 0?void 0:canonical(operation);if(key&&this.#operations.has(key))throw conflict("Feedback operation already belongs to another effect");const sequence=this.nextSequence(),next=Object.freeze({...previous,revision:previous.revision+1,feedback:Object.freeze([...previous.feedback,feedback]),receptions:operation?Object.freeze([...previous.receptions,Object.freeze({callId:toolCallId,operation})]):previous.receptions});return this.install(next,previous,sequence,key?{key,id:next.id,callId:toolCallId}:void 0,toolCallId),this.reference(next)})}read(originReference,value,exact=!0){const origin=this.origin(originReference),reference=requireExchangeReference(value),entry=this.#entries.get(reference.id);if(reference.collection!==this.id||!entry||entry.origin!==origin.id||reference.revision>entry.revision||exact&&reference.revision!==entry.revision)throw conflict("Exchange reference origin or revision mismatch");return entry}closed(originReference,references){const origin=this.origin(originReference);if(!Array.isArray(references)||isProxy(references)||references.length>this.#entryLimit)throw new TypeError("Invalid exchange reference list");const accepted=capture(references);if(!Array.isArray(accepted))throw new TypeError("Invalid exchange reference list");const ordered=this.#originEntries.get(origin.id);return Object.freeze(accepted.map((reference,index)=>{const entry=this.read(originReference,requireExchangeReference(reference));if(entry.id!==ordered[index])throw conflict("Exchange references are not an origin prefix");if(entry.reply.output.filter(item=>item.kind==="tool-call").length!==entry.feedback.length)throw conflict("Exchange is missing tool feedback");return entry}))}request(value){const prefix=record(capture(value),["origin","exchanges"]),reference=requireExchangeOriginReference(prefix.origin),origin=this.origin(reference),entries=this.closed(reference,prefix.exchanges);return Object.freeze(requireModelRequest({...origin.request,...origin.catalog.tools.length===0?{}:{tools:origin.catalog.tools},exchanges:Object.freeze(entries.map(entry=>Object.freeze({reply:entry.reply,feedback:entry.feedback})))}))}values(originReference){if(originReference===void 0)return Object.freeze([...this.#entries.values()]);const origin=this.origin(originReference);return Object.freeze(this.#originEntries.get(origin.id).map(id=>this.#entries.get(id)))}get(id){return this.#entries.get(identity(id))}reference(entry){if(this.#entries.get(entry.id)!==entry)throw conflict("Exchange entry is not the current entity value");return Object.freeze({collection:this.id,id:entry.id,revision:entry.revision})}snapshot(){return Object.freeze({formatVersion:2,id:this.id,sequence:this.sequence,origins:Object.freeze([...this.#origins.values()]),entries:this.values()})}static restore(value,options={}){const entity=new ModelExchanges(options),saved=captureSnapshotEnvelope(value,entity.#entryLimit,entity.#originLimit);if(saved.formatVersion!==2)throw new TypeError("Unsupported model exchange snapshot");entity.#id=identity(saved.id),entity.#sequence=counter(saved.sequence);const creations=[];let previousOrigin=0;for(const value2 of saved.origins){const origin=requireModelExchangeOrigin(value2);if(entity.#origins.has(origin.id)||origin.sequence<=previousOrigin)throw new TypeError("Invalid saved model exchange origin order");if(previousOrigin=origin.sequence,entity.#bytes+=encodedBytes(origin),entity.#bytes>entity.#byteLimit)throw new RangeError("Model exchange snapshot exceeds collection limits");entity.#origins.set(origin.id,origin),entity.#originEntries.set(origin.id,[]),creations.push({sequence:origin.sequence,revisions:0})}let previousCreated=0;for(const value2 of saved.entries){const entry=requireModelExchangeEntry(value2),origin=entity.#origins.get(entry.origin);if(entity.#entries.has(entry.id)||entity.#origins.has(entry.id)||!origin||origin.sequence>=entry.sequence||entry.sequence<=previousCreated||entry.sequence+entry.revision>entity.sequence)throw new TypeError("Invalid saved model exchange order");previousCreated=entry.sequence;const size=encodedBytes(entry);if(entity.#bytes+=size,entity.#bytes>entity.#byteLimit)throw new RangeError("Model exchange snapshot exceeds collection limits");for(const[operation,callId]of[[entry.operation,void 0],...entry.receptions.map(item=>[item.operation,item.callId])]){const key=canonical(operation);if(entity.#operations.has(key))throw new TypeError("Duplicate saved model exchange operation");entity.#operations.set(key,{id:entry.id,...callId===void 0?{}:{callId}})}entity.#entries.set(entry.id,entry),entity.#sizes.set(entry.id,size),entity.#originEntries.get(entry.origin).push(entry.id),creations.push({sequence:entry.sequence,revisions:entry.revision})}creations.sort((left,right)=>left.sequence-right.sequence);let changes=0,previousCreation=0;for(const creation of creations){if(creation.sequence<=previousCreation||creation.sequence>changes+1||creation.sequence>entity.sequence)throw new TypeError("Invalid saved model exchange creation sequence");if(previousCreation=creation.sequence,changes+=1+creation.revisions,!Number.isSafeInteger(changes))throw new TypeError("Invalid saved model exchange change count")}if(changes!==entity.sequence)throw new TypeError("Model exchange sequence does not match its changes");return entity}mutate(action){if(this.#persistenceFailure)throw this.#persistenceFailure;if(this.#mutating)throw new Error("Model exchange persistence or publication cannot reenter mutation");this.#mutating=!0;try{return action()}finally{this.#mutating=!1}}nextSequence(){if(!Number.isSafeInteger(this.#sequence+1))throw new RangeError("Model exchange sequence is exhausted");return this.#sequence+1}install(entry,previous,sequence,operation,toolCallId){const size=encodedBytes(entry),bytes=this.#bytes+size-(previous?this.#sizes.get(previous.id):0);if(size>maximumModelExchangeEntryBytes||bytes>this.#byteLimit||!previous&&this.#entries.size>=this.#entryLimit)throw new RangeError("Model exchange collection exceeds capacity");const change=Object.freeze({kind:"exchange",collection:this.id,expectedSequence:this.sequence,sequence,previous,entry});this.persist(change),previous||this.#originEntries.get(entry.origin).push(entry.id),this.#sizes.set(entry.id,size),this.#entries.set(entry.id,entry),this.#bytes=bytes,this.#sequence=sequence,operation&&this.#operations.set(operation.key,{id:operation.id,...operation.callId===void 0?{}:{callId:operation.callId}});const event=Object.freeze({type:"model-exchange",collection:this.id,sequence,reference:this.reference(entry),origin:entry.origin,kind:previous?"feedback":"intake",...toolCallId===void 0?{}:{toolCallId}});publishFact(event,this.#changes,this.#observation)}persist(change){try{const result=this.#persistence?.commit(change);if(result!==void 0)throw Promise.resolve(result).catch(()=>{}),new TypeError("Model exchange persistence must return undefined synchronously")}catch(cause){throw this.#persistenceFailure=cause instanceof PersistenceError?cause:new PersistenceError("model-exchanges.commit",cause),this.#persistenceFailure}}}function requireModelExchangeEntry(value){const item=record(capture(value),["id","origin","revision","sequence","operation","reply","feedback","receptions"]);if(!Array.isArray(item.feedback)||!Array.isArray(item.receptions))throw new TypeError("Invalid saved exchange feedback");const reply=requireModelReply(item.reply),feedback=Object.freeze(item.feedback.map(requireToolFeedback)),calls=new Set(reply.output.filter(entry=>entry.kind==="tool-call").map(entry=>entry.id)),completed=new Set(feedback.map(entry=>entry.toolCallId));if(completed.size!==feedback.length||feedback.some(entry=>!calls.has(entry.toolCallId))||item.revision!==feedback.length)throw new TypeError("Invalid saved exchange slots");const receptions=Object.freeze(item.receptions.map(value2=>{const entry=record(value2,["callId","operation"]),callId=identity(entry.callId);if(!completed.has(callId))throw new TypeError("Saved feedback operation has no corresponding slot");return Object.freeze({callId,operation:requireExchangeOperation(entry.operation)})}));if(new Set(receptions.map(item2=>item2.callId)).size!==receptions.length)throw new TypeError("Duplicate saved feedback operation");const sequence=counter(item.sequence);if(sequence===0)throw new TypeError("Exchange creation sequence must be positive");return Object.freeze({id:identity(item.id),origin:identity(item.origin),revision:counter(item.revision),sequence,operation:requireExchangeOperation(item.operation),reply,feedback,receptions})}__name(requireModelExchangeEntry,"requireModelExchangeEntry");function limit(value,maximum,name){if(!Number.isSafeInteger(value)||value<1||value>maximum)throw new RangeError(`Invalid model exchange ${name} limit`);return value}__name(limit,"limit");function conflict(message){return new RuntimeError("MODEL_EXCHANGE_CONFLICT",message)}__name(conflict,"conflict");export{ModelExchanges,requireModelExchangeEntry};
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export { ModelExchanges, requireModelExchangeEntry, type ModelExchangesOptions } from './exchanges.js';
2
- export { defaultModelExchangeLimits, maximumModelExchangeLimits, maximumModelExchangeEntryBytes } from './limits.js';
3
- export { ModelExchangeResource, type ModelExchangeSupply } from './resource.js';
2
+ export { defaultModelExchangeLimits, maximumModelExchangeLimits, maximumModelExchangeEntryBytes, maximumModelExchangeOriginBytes } from './limits.js';
3
+ export { ModelExchangeResource } from './resource.js';
4
4
  export { ModelExchangeViewSource, type ModelExchangeSummary } from './view.js';
5
- export { requireExchangeReference, requireExchangeOperation, requireExchangeIntakeReceipt, requireExchangeFeedbackReceipt, feedbackDigest } from './value.js';
6
- export type { ExchangeScope, ExchangeReference, ExchangeOperation, ExchangeIntakeReceipt, ExchangeFeedbackReceipt, ModelExchangeEntry, ModelExchangesState, ModelExchangesCommit, ModelExchangesPersistence, ModelExchangeChange } from './types.js';
5
+ export { requireExchangeReference, requireExchangeOriginReference, requireExchangeOperation, requireExchangeIntakeReceipt, requireExchangeFeedbackReceipt, feedbackDigest } from './value.js';
6
+ export { requireModelExchangeBasis, requireModelExchangeOrigin } from './origin.js';
7
+ export type { ModelExchangeBasis, ModelExchangeOrigin, ExchangeOriginReference, ExchangePrefix, ExchangeReference, ExchangeOperation, ExchangeIntakeReceipt, ExchangeFeedbackReceipt, ModelExchangeEntry, ModelExchangesState, ModelExchangesCommit, ModelExchangesPersistence, ModelExchangeChange } from './types.js';
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /*! MIT License | Copyright (c) 2026 wei.liang | See LICENSE */
2
- import{ModelExchanges,requireModelExchangeEntry}from"./exchanges.js";import{defaultModelExchangeLimits,maximumModelExchangeLimits,maximumModelExchangeEntryBytes}from"./limits.js";import{ModelExchangeResource}from"./resource.js";import{ModelExchangeViewSource}from"./view.js";import{requireExchangeReference,requireExchangeOperation,requireExchangeIntakeReceipt,requireExchangeFeedbackReceipt,feedbackDigest}from"./value.js";export{ModelExchangeResource,ModelExchangeViewSource,ModelExchanges,defaultModelExchangeLimits,feedbackDigest,maximumModelExchangeEntryBytes,maximumModelExchangeLimits,requireExchangeFeedbackReceipt,requireExchangeIntakeReceipt,requireExchangeOperation,requireExchangeReference,requireModelExchangeEntry};
2
+ import{ModelExchanges,requireModelExchangeEntry}from"./exchanges.js";import{defaultModelExchangeLimits,maximumModelExchangeLimits,maximumModelExchangeEntryBytes,maximumModelExchangeOriginBytes}from"./limits.js";import{ModelExchangeResource}from"./resource.js";import{ModelExchangeViewSource}from"./view.js";import{requireExchangeReference,requireExchangeOriginReference,requireExchangeOperation,requireExchangeIntakeReceipt,requireExchangeFeedbackReceipt,feedbackDigest}from"./value.js";import{requireModelExchangeBasis,requireModelExchangeOrigin}from"./origin.js";export{ModelExchangeResource,ModelExchangeViewSource,ModelExchanges,defaultModelExchangeLimits,feedbackDigest,maximumModelExchangeEntryBytes,maximumModelExchangeLimits,maximumModelExchangeOriginBytes,requireExchangeFeedbackReceipt,requireExchangeIntakeReceipt,requireExchangeOperation,requireExchangeOriginReference,requireExchangeReference,requireModelExchangeBasis,requireModelExchangeEntry,requireModelExchangeOrigin};
package/dist/limits.d.ts CHANGED
@@ -1,9 +1,12 @@
1
1
  export declare const defaultModelExchangeLimits: Readonly<{
2
+ origins: 10000;
2
3
  entries: 10000;
3
4
  bytes: number;
4
5
  }>;
5
6
  export declare const maximumModelExchangeLimits: Readonly<{
7
+ origins: 100000;
6
8
  entries: 100000;
7
9
  bytes: number;
8
10
  }>;
9
11
  export declare const maximumModelExchangeEntryBytes: number;
12
+ export declare const maximumModelExchangeOriginBytes: number;
package/dist/limits.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /*! MIT License | Copyright (c) 2026 wei.liang | See LICENSE */
2
- const defaultModelExchangeLimits=Object.freeze({entries:1e4,bytes:67108864}),maximumModelExchangeLimits=Object.freeze({entries:1e5,bytes:128*1024*1024}),maximumModelExchangeEntryBytes=8*1024*1024;export{defaultModelExchangeLimits,maximumModelExchangeEntryBytes,maximumModelExchangeLimits};
2
+ const defaultModelExchangeLimits=Object.freeze({origins:1e4,entries:1e4,bytes:67108864}),maximumModelExchangeLimits=Object.freeze({origins:1e5,entries:1e5,bytes:128*1024*1024}),maximumModelExchangeEntryBytes=8*1024*1024,maximumModelExchangeOriginBytes=8*1024*1024;export{defaultModelExchangeLimits,maximumModelExchangeEntryBytes,maximumModelExchangeLimits,maximumModelExchangeOriginBytes};
@@ -0,0 +1,4 @@
1
+ import type { ModelExchangeBasis, ModelExchangeOrigin } from './types.js';
2
+ export declare function requireModelExchangeBasis(value: unknown): ModelExchangeBasis;
3
+ export declare function requireModelExchangeOrigin(value: unknown): ModelExchangeOrigin;
4
+ export declare function exchangeOriginDigest(basis: ModelExchangeBasis): string;
package/dist/origin.js ADDED
@@ -0,0 +1,2 @@
1
+ /*! MIT License | Copyright (c) 2026 wei.liang | See LICENSE */
2
+ var __defProp=Object.defineProperty;var __name=(target,value)=>__defProp(target,"name",{value,configurable:!0});import{createHash}from"node:crypto";import{requireModelRequest}from"@weiliang0121/capix-model-provider";import{capture,counter,identity,record}from"./value.js";import{maximumModelExchangeOriginBytes}from"./limits.js";function requireModelExchangeBasis(value){const item=record(capture(value,maximumModelExchangeOriginBytes),["request","catalog"]),request=record(item.request,["messages","toolChoice","maxTokens","responseFormat","temperature","stream"]),catalog=record(item.catalog,["tools","digest","policyVersion"]),digest=identity(catalog.digest),policyVersion=identity(catalog.policyVersion);if(!Array.isArray(catalog.tools))throw new TypeError("Model exchange origin requires a tool catalog");const validated=requireModelRequest({...request,...catalog.tools.length===0?{}:{tools:catalog.tools}});return Object.freeze({request,catalog:Object.freeze({tools:validated.tools??Object.freeze([]),digest,policyVersion})})}__name(requireModelExchangeBasis,"requireModelExchangeBasis");function requireModelExchangeOrigin(value){const item=record(capture(value,maximumModelExchangeOriginBytes),["id","sequence","digest","request","catalog"]),basis=requireModelExchangeBasis({request:item.request,catalog:item.catalog}),sequence=counter(item.sequence);if(sequence===0||item.digest!==exchangeOriginDigest(basis))throw new TypeError("Invalid saved model exchange origin");return Object.freeze({id:identity(item.id),sequence,digest:item.digest,...basis})}__name(requireModelExchangeOrigin,"requireModelExchangeOrigin");function exchangeOriginDigest(basis){function encode(value){return Array.isArray(value)?`[${value.map(encode).join(",")}]`:value!==null&&typeof value=="object"?`{${Object.keys(value).sort().map(key=>`${JSON.stringify(key)}:${encode(value[key])}`).join(",")}}`:typeof value=="number"&&Object.is(value,-0)?"-0":JSON.stringify(value)}return __name(encode,"encode"),createHash("sha256").update(encode(basis)).digest("hex")}__name(exchangeOriginDigest,"exchangeOriginDigest");export{exchangeOriginDigest,requireModelExchangeBasis,requireModelExchangeOrigin};
@@ -1,15 +1,14 @@
1
1
  import { ResourceOffer } from '@weiliang0121/capix-resources';
2
2
  import type { ModelReply, ToolResult } from '@weiliang0121/capix-model-provider';
3
3
  import type { ModelExchanges } from './exchanges.js';
4
- import type { ExchangeFeedbackReceipt, ExchangeIntakeReceipt, ExchangeReference } from './types.js';
5
- export interface ModelExchangeSupply {
6
- readonly exchanges: ModelExchanges;
7
- readonly scope: string;
8
- }
9
- export declare const ModelExchangeResource: ResourceOffer<ModelExchangeSupply, Readonly<{
10
- intake(reply: ModelReply): ExchangeIntakeReceipt;
11
- read(reference: ExchangeReference): import("./types.js").ModelExchangeEntry;
12
- closed(references: readonly ExchangeReference[]): readonly import("./types.js").ModelExchangeEntry[];
13
- receive(reference: ExchangeReference, callId: string, result: ToolResult): ExchangeFeedbackReceipt;
14
- complete(reference: ExchangeReference, callId: string, result: ToolResult): ExchangeReference;
15
- }>, ModelExchangeSupply, never>;
4
+ import type { ExchangeFeedbackReceipt, ExchangeIntakeReceipt, ExchangeReference, ExchangeOriginReference, ExchangePrefix, ModelExchangeBasis } from './types.js';
5
+ export declare const ModelExchangeResource: ResourceOffer<ModelExchanges, Readonly<{
6
+ begin(id: string, basis: ModelExchangeBasis): ExchangeOriginReference;
7
+ origin(reference: ExchangeOriginReference): import("./types.js").ModelExchangeOrigin;
8
+ request(prefix: ExchangePrefix): import("@weiliang0121/capix-model-provider").ModelRequest;
9
+ intake(origin: ExchangeOriginReference, reply: ModelReply): ExchangeIntakeReceipt;
10
+ read(origin: ExchangeOriginReference, reference: ExchangeReference, exact?: boolean): import("./types.js").ModelExchangeEntry;
11
+ closed(origin: ExchangeOriginReference, references: readonly ExchangeReference[]): readonly import("./types.js").ModelExchangeEntry[];
12
+ receive(origin: ExchangeOriginReference, reference: ExchangeReference, callId: string, result: ToolResult): ExchangeFeedbackReceipt;
13
+ complete(origin: ExchangeOriginReference, reference: ExchangeReference, callId: string, result: ToolResult): ExchangeReference;
14
+ }>, ModelExchanges, never>;
package/dist/resource.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /*! MIT License | Copyright (c) 2026 wei.liang | See LICENSE */
2
- var __defProp=Object.defineProperty;var __name=(target,value)=>__defProp(target,"name",{value,configurable:!0});import{ResourceOffer}from"@weiliang0121/capix-resources";import{feedbackDigest,identity,requireExchangeOperation}from"./value.js";const ModelExchangeResource=new ResourceOffer({name:"model-exchanges",timing:"access",expose:__name(({exchanges,scope},activity)=>(scope=identity(scope),Object.freeze({intake(reply){return activity.runSync("intake",{},()=>{const operation=requireExchangeOperation({...activity.reception(),position:"model-reply"});return Object.freeze({reference:exchanges.intake(scope,operation,reply),operation})})},read(reference){return activity.runSync("read",{},()=>exchanges.read(scope,reference,!1))},closed(references){return activity.runSync("closed",{},()=>exchanges.closed(scope,references))},receive(reference,callId,result){return activity.runSync("receive",{},()=>{const operation=requireExchangeOperation({...activity.reception(),position:"tool-feedback"}),next=exchanges.complete(scope,reference,callId,result,operation),feedback=exchanges.read(scope,next).feedback.find(item=>item.toolCallId===callId);return Object.freeze({reference:next,operation,feedbackDigest:feedbackDigest(feedback)})})},complete(reference,callId,result){return activity.runSync("complete",{},()=>exchanges.complete(scope,reference,callId,result))}})),"expose")});export{ModelExchangeResource};
2
+ var __defProp=Object.defineProperty;var __name=(target,value)=>__defProp(target,"name",{value,configurable:!0});import{ResourceOffer}from"@weiliang0121/capix-resources";import{feedbackDigest,requireExchangeOperation}from"./value.js";const ModelExchangeResource=new ResourceOffer({name:"model-exchanges",timing:"access",expose:__name((exchanges,activity)=>Object.freeze({begin(id,basis){return activity.runSync("begin",{},()=>exchanges.begin(id,basis))},origin(reference){return activity.runSync("origin",{},()=>exchanges.origin(reference))},request(prefix){return activity.runSync("request",{},()=>exchanges.request(prefix))},intake(origin,reply){return activity.runSync("intake",{},()=>{const operation=requireExchangeOperation({...activity.reception(),position:"model-reply"});return Object.freeze({reference:exchanges.intake(origin,operation,reply),operation})})},read(origin,reference,exact=!0){return activity.runSync("read",{},()=>exchanges.read(origin,reference,exact))},closed(origin,references){return activity.runSync("closed",{},()=>exchanges.closed(origin,references))},receive(origin,reference,callId,result){return activity.runSync("receive",{},()=>{const operation=requireExchangeOperation({...activity.reception(),position:"tool-feedback"}),next=exchanges.complete(origin,reference,callId,result,operation),feedback=exchanges.read(origin,next).feedback.find(item=>item.toolCallId===callId);return Object.freeze({reference:next,operation,feedbackDigest:feedbackDigest(feedback)})})},complete(origin,reference,callId,result){return activity.runSync("complete",{},()=>exchanges.complete(origin,reference,callId,result))}}),"expose")});export{ModelExchangeResource};
package/dist/types.d.ts CHANGED
@@ -1,5 +1,26 @@
1
- import type { ModelReply, ToolFeedback } from '@weiliang0121/capix-model-provider';
2
- export type ExchangeScope = string;
1
+ import type { ModelReply, ModelRequest, ModelTool, ToolFeedback } from '@weiliang0121/capix-model-provider';
2
+ export interface ModelExchangeBasis {
3
+ readonly request: Omit<ModelRequest, 'tools' | 'exchanges'>;
4
+ readonly catalog: {
5
+ readonly tools: readonly ModelTool[];
6
+ readonly digest: string;
7
+ readonly policyVersion: string;
8
+ };
9
+ }
10
+ export interface ModelExchangeOrigin extends ModelExchangeBasis {
11
+ readonly id: string;
12
+ readonly sequence: number;
13
+ readonly digest: string;
14
+ }
15
+ export interface ExchangeOriginReference {
16
+ readonly collection: string;
17
+ readonly id: string;
18
+ readonly digest: string;
19
+ }
20
+ export interface ExchangePrefix {
21
+ readonly origin: ExchangeOriginReference;
22
+ readonly exchanges: readonly ExchangeReference[];
23
+ }
3
24
  export interface ExchangeReference {
4
25
  readonly collection: string;
5
26
  readonly id: string;
@@ -19,7 +40,7 @@ export interface ExchangeFeedbackReceipt extends ExchangeIntakeReceipt {
19
40
  }
20
41
  export interface ModelExchangeEntry {
21
42
  readonly id: string;
22
- readonly scope: ExchangeScope;
43
+ readonly origin: string;
23
44
  readonly revision: number;
24
45
  readonly sequence: number;
25
46
  readonly operation: ExchangeOperation;
@@ -31,27 +52,40 @@ export interface ModelExchangeEntry {
31
52
  }[];
32
53
  }
33
54
  export interface ModelExchangesState {
34
- readonly formatVersion: 1;
55
+ readonly formatVersion: 2;
35
56
  readonly id: string;
36
57
  readonly sequence: number;
58
+ readonly origins: readonly ModelExchangeOrigin[];
37
59
  readonly entries: readonly ModelExchangeEntry[];
38
60
  }
39
- export interface ModelExchangesCommit {
61
+ interface ModelExchangesCommitBasis {
40
62
  readonly collection: string;
41
63
  readonly expectedSequence: number;
42
64
  readonly sequence: number;
65
+ }
66
+ export type ModelExchangesCommit = ModelExchangesCommitBasis & ({
67
+ readonly kind: 'origin';
68
+ readonly origin: ModelExchangeOrigin;
69
+ } | {
70
+ readonly kind: 'exchange';
43
71
  readonly previous: ModelExchangeEntry | null;
44
72
  readonly entry: ModelExchangeEntry;
45
- }
73
+ });
46
74
  export interface ModelExchangesPersistence {
47
75
  commit(change: ModelExchangesCommit): undefined;
48
76
  }
49
- export interface ModelExchangeChange {
77
+ interface ModelExchangeChangeBasis {
50
78
  readonly type: 'model-exchange';
51
79
  readonly collection: string;
52
80
  readonly sequence: number;
53
- readonly reference: ExchangeReference;
54
- readonly scope: ExchangeScope;
81
+ readonly origin: string;
82
+ }
83
+ export type ModelExchangeChange = ModelExchangeChangeBasis & ({
84
+ readonly kind: 'origin';
85
+ readonly reference: ExchangeOriginReference;
86
+ } | {
55
87
  readonly kind: 'intake' | 'feedback';
88
+ readonly reference: ExchangeReference;
56
89
  readonly toolCallId?: string;
57
- }
90
+ });
91
+ export {};
package/dist/value.d.ts CHANGED
@@ -1,12 +1,13 @@
1
- import type { ExchangeOperation, ExchangeReference, ExchangeIntakeReceipt, ExchangeFeedbackReceipt } from './types.js';
1
+ import type { ExchangeOperation, ExchangeReference, ExchangeOriginReference, ExchangeIntakeReceipt, ExchangeFeedbackReceipt } from './types.js';
2
2
  export type JsonValue = null | boolean | number | string | readonly JsonValue[] | {
3
3
  readonly [name: string]: JsonValue;
4
4
  };
5
5
  export declare function capture(value: unknown, limit?: number): JsonValue;
6
- export declare function captureSnapshotEnvelope(value: unknown, maximumEntries: number): Readonly<{
6
+ export declare function captureSnapshotEnvelope(value: unknown, maximumEntries: number, maximumOrigins: number): Readonly<{
7
7
  formatVersion: unknown;
8
8
  id: unknown;
9
9
  sequence: unknown;
10
+ origins: readonly unknown[];
10
11
  entries: readonly unknown[];
11
12
  }>;
12
13
  export declare function encodedBytes(value: unknown): number;
@@ -15,6 +16,7 @@ export declare function record(value: unknown, keys: readonly string[]): Record<
15
16
  export declare function identity(value: unknown): string;
16
17
  export declare function counter(value: unknown): number;
17
18
  export declare function requireExchangeReference(value: unknown): ExchangeReference;
19
+ export declare function requireExchangeOriginReference(value: unknown): ExchangeOriginReference;
18
20
  export declare function requireExchangeOperation(value: unknown): ExchangeOperation;
19
21
  export declare function requireExchangeIntakeReceipt(value: unknown): ExchangeIntakeReceipt;
20
22
  export declare function requireExchangeFeedbackReceipt(value: unknown): ExchangeFeedbackReceipt;
package/dist/value.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /*! MIT License | Copyright (c) 2026 wei.liang | See LICENSE */
2
- var __defProp=Object.defineProperty;var __name=(target,value)=>__defProp(target,"name",{value,configurable:!0});import{createHash}from"node:crypto";import{isProxy}from"node:util/types";import{requireToolFeedback}from"@weiliang0121/capix-model-provider";import{maximumModelExchangeEntryBytes}from"./limits.js";function capture(value,limit=maximumModelExchangeEntryBytes){let nodes=0,bytes=0;const ancestors=new Set;function visit(item,depth){if(++nodes>2e6||depth>64)throw new TypeError("Model exchange value exceeds structural limits");if(item===null||typeof item=="boolean")return item;if(typeof item=="string"){if(bytes+=Buffer.byteLength(item),bytes>limit)throw new RangeError("Model exchange value exceeds byte limit");return item}if(typeof item=="number"&&Number.isFinite(item))return item;if(typeof item!="object"||isProxy(item)||ancestors.has(item))throw new TypeError("Model exchange requires acyclic JSON data");const array=Array.isArray(item);if(!array&&Object.getPrototypeOf(item)!==Object.prototype&&Object.getPrototypeOf(item)!==null)throw new TypeError("Model exchange requires plain JSON data");if(Object.getOwnPropertySymbols(item).length)throw new TypeError("Model exchange does not accept symbol properties");const result2=array?[]:{};ancestors.add(item);const descriptors=Object.getOwnPropertyDescriptors(item);for(const[key,descriptor]of Object.entries(descriptors))if(!(array&&key==="length")){if(!descriptor.enumerable||!("value"in descriptor))throw new TypeError("Model exchange does not accept accessors or hidden properties");if(array&&(!/^(0|[1-9][0-9]*)$/u.test(key)||Number(key)>=item.length))throw new TypeError("Model exchange array contains extra properties");if(bytes+=Buffer.byteLength(key),bytes>limit)throw new RangeError("Model exchange value exceeds byte limit");Object.defineProperty(result2,key,{value:visit(descriptor.value,depth+1),enumerable:!0})}if(array&&Object.keys(descriptors).length!==item.length+1)throw new TypeError("Model exchange does not accept sparse arrays");return ancestors.delete(item),Object.freeze(result2)}__name(visit,"visit");const result=visit(value,0);if(encodedBytes(result)>limit)throw new RangeError("Model exchange value exceeds byte limit");return result}__name(capture,"capture");function captureSnapshotEnvelope(value,maximumEntries){if(value===null||typeof value!="object"||isProxy(value)||Array.isArray(value)||Object.getPrototypeOf(value)!==Object.prototype&&Object.getPrototypeOf(value)!==null)throw new TypeError("Invalid model exchange snapshot object");const fields=Object.getOwnPropertyDescriptors(value),names=["formatVersion","id","sequence","entries"];if(Reflect.ownKeys(fields).length!==names.length||names.some(name=>!Object.hasOwn(fields,name)||!fields[name].enumerable||!("value"in fields[name])))throw new TypeError("Invalid model exchange snapshot fields");const source=fields.entries.value;if(!Array.isArray(source)||isProxy(source)||Object.getPrototypeOf(source)!==Array.prototype)throw new TypeError("Invalid model exchange entries array");if(source.length>maximumEntries)throw new RangeError("Model exchange snapshot exceeds collection limits");if(Reflect.ownKeys(source).length!==source.length+1)throw new TypeError("Model exchange entries must be a dense data array");const entries=[];for(let index=0;index<source.length;index++){const field=Object.getOwnPropertyDescriptor(source,String(index));if(!field||!field.enumerable||!("value"in field))throw new TypeError("Model exchange entries must be a dense data array");entries.push(field.value)}return Object.freeze({formatVersion:fields.formatVersion.value,id:fields.id.value,sequence:fields.sequence.value,entries:Object.freeze(entries)})}__name(captureSnapshotEnvelope,"captureSnapshotEnvelope");function encodedBytes(value){return Buffer.byteLength(JSON.stringify(value))}__name(encodedBytes,"encodedBytes");function canonical(value){const accepted=capture(value);function encode(item){return Array.isArray(item)?`[${item.map(encode).join(",")}]`:item!==null&&typeof item=="object"?`{${Object.keys(item).sort().map(key=>`${JSON.stringify(key)}:${encode(item[key])}`).join(",")}}`:JSON.stringify(item)}return __name(encode,"encode"),encode(accepted)}__name(canonical,"canonical");function record(value,keys){if(value===null||typeof value!="object"||Array.isArray(value)||Object.keys(value).some(key=>!keys.includes(key)))throw new TypeError("Invalid model exchange object");return value}__name(record,"record");function identity(value){if(typeof value!="string"||!value.trim()||value.length>512)throw new TypeError("Invalid model exchange identity");return value}__name(identity,"identity");function counter(value){if(typeof value!="number"||!Number.isSafeInteger(value)||value<0)throw new TypeError("Invalid model exchange revision");return value}__name(counter,"counter");function requireExchangeReference(value){const item=record(capture(value),["collection","id","revision"]);return Object.freeze({collection:identity(item.collection),id:identity(item.id),revision:counter(item.revision)})}__name(requireExchangeReference,"requireExchangeReference");function requireExchangeOperation(value){const item=record(capture(value),["parentContextId","childContextId","position"]);return Object.freeze({parentContextId:identity(item.parentContextId),childContextId:identity(item.childContextId),position:identity(item.position)})}__name(requireExchangeOperation,"requireExchangeOperation");function requireExchangeIntakeReceipt(value){const item=record(capture(value),["reference","operation"]);return Object.freeze({reference:requireExchangeReference(item.reference),operation:requireExchangeOperation(item.operation)})}__name(requireExchangeIntakeReceipt,"requireExchangeIntakeReceipt");function requireExchangeFeedbackReceipt(value){const item=record(capture(value),["reference","operation","feedbackDigest"]);if(typeof item.feedbackDigest!="string"||!/^[a-f0-9]{64}$/u.test(item.feedbackDigest))throw new TypeError("Invalid model exchange feedback digest");return Object.freeze({reference:requireExchangeReference(item.reference),operation:requireExchangeOperation(item.operation),feedbackDigest:item.feedbackDigest})}__name(requireExchangeFeedbackReceipt,"requireExchangeFeedbackReceipt");function feedbackDigest(value){return createHash("sha256").update(canonical(requireToolFeedback(capture(value)))).digest("hex")}__name(feedbackDigest,"feedbackDigest");export{canonical,capture,captureSnapshotEnvelope,counter,encodedBytes,feedbackDigest,identity,record,requireExchangeFeedbackReceipt,requireExchangeIntakeReceipt,requireExchangeOperation,requireExchangeReference};
2
+ var __defProp=Object.defineProperty;var __name=(target,value)=>__defProp(target,"name",{value,configurable:!0});import{createHash}from"node:crypto";import{isProxy}from"node:util/types";import{requireToolFeedback}from"@weiliang0121/capix-model-provider";import{maximumModelExchangeEntryBytes}from"./limits.js";function capture(value,limit=maximumModelExchangeEntryBytes){let nodes=0,bytes=0;const ancestors=new Set;function visit(item,depth){if(++nodes>2e6||depth>64)throw new TypeError("Model exchange value exceeds structural limits");if(item===null||typeof item=="boolean")return item;if(typeof item=="string"){if(bytes+=Buffer.byteLength(item),bytes>limit)throw new RangeError("Model exchange value exceeds byte limit");return item}if(typeof item=="number"&&Number.isFinite(item))return item;if(typeof item!="object"||isProxy(item)||ancestors.has(item))throw new TypeError("Model exchange requires acyclic JSON data");const array=Array.isArray(item);if(!array&&Object.getPrototypeOf(item)!==Object.prototype&&Object.getPrototypeOf(item)!==null)throw new TypeError("Model exchange requires plain JSON data");if(Object.getOwnPropertySymbols(item).length)throw new TypeError("Model exchange does not accept symbol properties");const result2=array?[]:{};ancestors.add(item);const descriptors=Object.getOwnPropertyDescriptors(item);for(const[key,descriptor]of Object.entries(descriptors))if(!(array&&key==="length")){if(!descriptor.enumerable||!("value"in descriptor))throw new TypeError("Model exchange does not accept accessors or hidden properties");if(array&&(!/^(0|[1-9][0-9]*)$/u.test(key)||Number(key)>=item.length))throw new TypeError("Model exchange array contains extra properties");if(bytes+=Buffer.byteLength(key),bytes>limit)throw new RangeError("Model exchange value exceeds byte limit");Object.defineProperty(result2,key,{value:visit(descriptor.value,depth+1),enumerable:!0})}if(array&&Object.keys(descriptors).length!==item.length+1)throw new TypeError("Model exchange does not accept sparse arrays");return ancestors.delete(item),Object.freeze(result2)}__name(visit,"visit");const result=visit(value,0);if(encodedBytes(result)>limit)throw new RangeError("Model exchange value exceeds byte limit");return result}__name(capture,"capture");function captureSnapshotEnvelope(value,maximumEntries,maximumOrigins){if(value===null||typeof value!="object"||isProxy(value)||Array.isArray(value)||Object.getPrototypeOf(value)!==Object.prototype&&Object.getPrototypeOf(value)!==null)throw new TypeError("Invalid model exchange snapshot object");const fields=Object.getOwnPropertyDescriptors(value),names=["formatVersion","id","sequence","origins","entries"];if(Reflect.ownKeys(fields).length!==names.length||names.some(name=>!Object.hasOwn(fields,name)||!fields[name].enumerable||!("value"in fields[name])))throw new TypeError("Invalid model exchange snapshot fields");const origins=captureSnapshotArray(fields.origins.value,maximumOrigins),entries=captureSnapshotArray(fields.entries.value,maximumEntries);return Object.freeze({formatVersion:fields.formatVersion.value,id:fields.id.value,sequence:fields.sequence.value,origins,entries})}__name(captureSnapshotEnvelope,"captureSnapshotEnvelope");function captureSnapshotArray(source,maximum){if(!Array.isArray(source)||isProxy(source)||Object.getPrototypeOf(source)!==Array.prototype)throw new TypeError("Invalid model exchange entries array");if(source.length>maximum)throw new RangeError("Model exchange snapshot exceeds collection limits");if(Reflect.ownKeys(source).length!==source.length+1)throw new TypeError("Model exchange entries must be a dense data array");const entries=[];for(let index=0;index<source.length;index++){const field=Object.getOwnPropertyDescriptor(source,String(index));if(!field||!field.enumerable||!("value"in field))throw new TypeError("Model exchange entries must be a dense data array");entries.push(field.value)}return Object.freeze(entries)}__name(captureSnapshotArray,"captureSnapshotArray");function encodedBytes(value){return Buffer.byteLength(JSON.stringify(value))}__name(encodedBytes,"encodedBytes");function canonical(value){const accepted=capture(value);function encode(item){return Array.isArray(item)?`[${item.map(encode).join(",")}]`:item!==null&&typeof item=="object"?`{${Object.keys(item).sort().map(key=>`${JSON.stringify(key)}:${encode(item[key])}`).join(",")}}`:JSON.stringify(item)}return __name(encode,"encode"),encode(accepted)}__name(canonical,"canonical");function record(value,keys){if(value===null||typeof value!="object"||Array.isArray(value)||Object.keys(value).some(key=>!keys.includes(key)))throw new TypeError("Invalid model exchange object");return value}__name(record,"record");function identity(value){if(typeof value!="string"||!value.trim()||value.length>512)throw new TypeError("Invalid model exchange identity");return value}__name(identity,"identity");function counter(value){if(typeof value!="number"||!Number.isSafeInteger(value)||value<0)throw new TypeError("Invalid model exchange revision");return value}__name(counter,"counter");function requireExchangeReference(value){const item=record(capture(value),["collection","id","revision"]);return Object.freeze({collection:identity(item.collection),id:identity(item.id),revision:counter(item.revision)})}__name(requireExchangeReference,"requireExchangeReference");function requireExchangeOriginReference(value){const item=record(capture(value),["collection","id","digest"]);if(typeof item.digest!="string"||!/^[a-f0-9]{64}$/u.test(item.digest))throw new TypeError("Invalid model exchange origin digest");return Object.freeze({collection:identity(item.collection),id:identity(item.id),digest:item.digest})}__name(requireExchangeOriginReference,"requireExchangeOriginReference");function requireExchangeOperation(value){const item=record(capture(value),["parentContextId","childContextId","position"]);return Object.freeze({parentContextId:identity(item.parentContextId),childContextId:identity(item.childContextId),position:identity(item.position)})}__name(requireExchangeOperation,"requireExchangeOperation");function requireExchangeIntakeReceipt(value){const item=record(capture(value),["reference","operation"]);return Object.freeze({reference:requireExchangeReference(item.reference),operation:requireExchangeOperation(item.operation)})}__name(requireExchangeIntakeReceipt,"requireExchangeIntakeReceipt");function requireExchangeFeedbackReceipt(value){const item=record(capture(value),["reference","operation","feedbackDigest"]);if(typeof item.feedbackDigest!="string"||!/^[a-f0-9]{64}$/u.test(item.feedbackDigest))throw new TypeError("Invalid model exchange feedback digest");return Object.freeze({reference:requireExchangeReference(item.reference),operation:requireExchangeOperation(item.operation),feedbackDigest:item.feedbackDigest})}__name(requireExchangeFeedbackReceipt,"requireExchangeFeedbackReceipt");function feedbackDigest(value){return createHash("sha256").update(canonical(requireToolFeedback(capture(value)))).digest("hex")}__name(feedbackDigest,"feedbackDigest");export{canonical,capture,captureSnapshotEnvelope,counter,encodedBytes,feedbackDigest,identity,record,requireExchangeFeedbackReceipt,requireExchangeIntakeReceipt,requireExchangeOperation,requireExchangeOriginReference,requireExchangeReference};
package/dist/view.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { SourceStamp, SyncViewSource } from '@weiliang0121/capix-view';
2
2
  import type { ModelExchanges } from './exchanges.js';
3
- import type { ModelExchangeEntry } from './types.js';
3
+ import type { ExchangeOriginReference, ModelExchangeEntry } from './types.js';
4
4
  export interface ModelExchangeSummary {
5
5
  readonly model: string;
6
6
  readonly replyId: string;
@@ -12,7 +12,7 @@ export declare class ModelExchangeViewSource implements Source {
12
12
  #private;
13
13
  readonly id: string;
14
14
  readonly scope: string;
15
- constructor(exchanges: ModelExchanges, scope?: string);
15
+ constructor(exchanges: ModelExchanges, origin?: ExchangeOriginReference);
16
16
  index(): ReturnType<Source['index']>;
17
17
  inspect(keys: readonly string[]): ReturnType<Source['inspect']>;
18
18
  read(keys: readonly string[], expected: SourceStamp): ReturnType<Source['read']>;
package/dist/view.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /*! MIT License | Copyright (c) 2026 wei.liang | See LICENSE */
2
- var __defProp=Object.defineProperty;var __name=(target,value)=>__defProp(target,"name",{value,configurable:!0});import{RuntimeError}from"@weiliang0121/capix-contracts";import{capture,identity,record}from"./value.js";class ModelExchangeViewSource{static{__name(this,"ModelExchangeViewSource")}id;scope;#exchanges;#group;constructor(exchanges,scope){this.#exchanges=exchanges,this.#group=scope===void 0?void 0:identity(scope),this.id=exchanges.id,this.scope=scope===void 0?"whole":`scope:${JSON.stringify(scope)}`,Object.freeze(this)}index(){const stamp=this.stamp(),entries=Object.freeze(this.#exchanges.values(this.#group).map(entry=>Object.freeze({key:entry.id,summary:summarize(entry)})));return this.requireCurrent(stamp),Object.freeze({stamp,entries})}inspect(keys){const stamp=this.stamp(),entries=Object.freeze(this.select(keys).map(entry=>Object.freeze({key:entry.id,summary:summarize(entry)})));return this.requireCurrent(stamp),Object.freeze({stamp,entries})}read(keys,expected){const value=record(capture(expected),["id","scope","version"]);if(typeof value.id!="string"||typeof value.scope!="string"||typeof value.version!="string")throw new TypeError("Invalid model exchange source stamp");const basis=Object.freeze({id:value.id,scope:value.scope,version:value.version});this.requireCurrent(basis);const entries=Object.freeze(this.select(keys).map(entry=>Object.freeze({key:entry.id,value:entry})));return this.requireCurrent(basis),Object.freeze({stamp:this.stamp(),entries})}check(expected){return expected?.id===this.id&&expected.scope===this.scope&&expected.version===String(this.#exchanges.sequence)}stamp(){return Object.freeze({id:this.id,scope:this.scope,version:String(this.#exchanges.sequence)})}requireCurrent(expected){if(!this.check(expected))throw new RuntimeError("VIEW_SOURCE_CONFLICT","Model exchange view source has changed")}select(keys){const selected=capture(keys);if(!Array.isArray(selected)||selected.some(key=>typeof key!="string"||!key)||new Set(selected).size!==selected.length)throw new TypeError("Model exchange view requires unique string keys");return selected.map(key=>{const entry=this.#exchanges.get(identity(key));if(!entry||this.#group!==void 0&&entry.scope!==this.#group)throw new RangeError("Exchange is outside the view source scope");return entry})}}function summarize(entry){return Object.freeze({model:entry.reply.model,replyId:entry.reply.id,toolCalls:entry.reply.output.filter(item=>item.kind==="tool-call").length,completed:entry.feedback.length})}__name(summarize,"summarize");export{ModelExchangeViewSource};
2
+ var __defProp=Object.defineProperty;var __name=(target,value)=>__defProp(target,"name",{value,configurable:!0});import{RuntimeError}from"@weiliang0121/capix-contracts";import{capture,identity,record,requireExchangeOriginReference}from"./value.js";class ModelExchangeViewSource{static{__name(this,"ModelExchangeViewSource")}id;scope;#exchanges;#origin;constructor(exchanges,origin){this.#exchanges=exchanges,this.#origin=origin===void 0?void 0:requireExchangeOriginReference(origin),this.#origin&&exchanges.origin(this.#origin),this.id=exchanges.id,this.scope=this.#origin===void 0?"whole":`origin:${JSON.stringify(this.#origin.id)}`,Object.freeze(this)}index(){const stamp=this.stamp(),entries=Object.freeze(this.#exchanges.values(this.#origin).map(entry=>Object.freeze({key:entry.id,summary:summarize(entry)})));return this.requireCurrent(stamp),Object.freeze({stamp,entries})}inspect(keys){const stamp=this.stamp(),entries=Object.freeze(this.select(keys).map(entry=>Object.freeze({key:entry.id,summary:summarize(entry)})));return this.requireCurrent(stamp),Object.freeze({stamp,entries})}read(keys,expected){const value=record(capture(expected),["id","scope","version"]);if(typeof value.id!="string"||typeof value.scope!="string"||typeof value.version!="string")throw new TypeError("Invalid model exchange source stamp");const basis=Object.freeze({id:value.id,scope:value.scope,version:value.version});this.requireCurrent(basis);const entries=Object.freeze(this.select(keys).map(entry=>Object.freeze({key:entry.id,value:entry})));return this.requireCurrent(basis),Object.freeze({stamp:this.stamp(),entries})}check(expected){return expected?.id===this.id&&expected.scope===this.scope&&expected.version===String(this.#exchanges.sequence)}stamp(){return Object.freeze({id:this.id,scope:this.scope,version:String(this.#exchanges.sequence)})}requireCurrent(expected){if(!this.check(expected))throw new RuntimeError("VIEW_SOURCE_CONFLICT","Model exchange view source has changed")}select(keys){const selected=capture(keys);if(!Array.isArray(selected)||selected.some(key=>typeof key!="string"||!key)||new Set(selected).size!==selected.length)throw new TypeError("Model exchange view requires unique string keys");return selected.map(key=>{const entry=this.#exchanges.get(identity(key));if(!entry||this.#origin!==void 0&&entry.origin!==this.#origin.id)throw new RangeError("Exchange is outside the view source scope");return entry})}}function summarize(entry){return Object.freeze({model:entry.reply.model,replyId:entry.reply.id,toolCalls:entry.reply.output.filter(item=>item.kind==="tool-call").length,completed:entry.feedback.length})}__name(summarize,"summarize");export{ModelExchangeViewSource};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weiliang0121/capix-model-exchanges",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -10,12 +10,12 @@
10
10
  }
11
11
  },
12
12
  "dependencies": {
13
- "@weiliang0121/capix-contracts": "0.1.0",
14
- "@weiliang0121/capix-changes": "0.1.0",
15
- "@weiliang0121/capix-diagnostics": "0.1.0",
16
- "@weiliang0121/capix-model-provider": "0.1.0",
17
- "@weiliang0121/capix-view": "0.1.0",
18
- "@weiliang0121/capix-resources": "0.1.0"
13
+ "@weiliang0121/capix-contracts": "0.3.0",
14
+ "@weiliang0121/capix-diagnostics": "0.3.0",
15
+ "@weiliang0121/capix-changes": "0.3.0",
16
+ "@weiliang0121/capix-resources": "0.3.0",
17
+ "@weiliang0121/capix-model-provider": "0.3.0",
18
+ "@weiliang0121/capix-view": "0.3.0"
19
19
  },
20
20
  "description": "Model reply and tool feedback entities for CAPiX.",
21
21
  "license": "MIT",
@@ -23,7 +23,7 @@
23
23
  "repository": {
24
24
  "type": "git",
25
25
  "url": "git+https://github.com/weiliang0121/capix.git",
26
- "directory": "server/model-exchanges"
26
+ "directory": "packages/model-exchanges"
27
27
  },
28
28
  "engines": {
29
29
  "node": ">=22.23.2"
@@ -42,6 +42,7 @@
42
42
  },
43
43
  "scripts": {
44
44
  "build": "tsc -p tsconfig.json",
45
- "typecheck": "tsc -p tsconfig.json --noEmit"
45
+ "typecheck": "tsc -p tsconfig.json --noEmit",
46
+ "test": "node ../../scripts/test/run.mjs --package ."
46
47
  }
47
48
  }