@weiliang0121/capix-tool 0.0.0-stage → 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 wei.liang (https://github.com/weiliang0121)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,104 @@
1
- # Temporary Holding Version
1
+ # @weiliang0121/capix-tool
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `Tool` 是组织原生模型工具调用的普通 Group 预设,与 Sequence、Dynamic 使用同一结构和调用机制。模型选择已登记的 Node / Group,成员使用自己的资源,结果回填同一个交换起点后继续请求模型。
4
+
5
+ ESM、TypeScript;Node.js ≥ 22.23.2。通过 `npm install @weiliang0121/capix-tool` 安装。
6
+
7
+ ## 定义处理结构
8
+
9
+ ```ts
10
+ import { z } from 'zod';
11
+ import { Node, Scene } from '@weiliang0121/capix-structure';
12
+ import { Tool } from '@weiliang0121/capix-tool';
13
+
14
+ const lookup = new Node<{ word: string }, string>({
15
+ run: (work) => (work.input.word === 'hello' ? '你好' : work.input.word),
16
+ });
17
+ const answer = new Tool({
18
+ policyVersion: 'dictionary.v1',
19
+ tools: {
20
+ lookup: {
21
+ node: lookup,
22
+ description: 'Look up a Chinese translation.',
23
+ input: z.strictObject({ word: z.string() }),
24
+ result: { succeeded: (text) => ({ status: 'ok', content: [{ kind: 'text', text }] }) },
25
+ },
26
+ },
27
+ });
28
+ const scene = new Scene(answer);
29
+ ```
30
+
31
+ `tools` 的键同时是模型工具名和成员选择名,不再逐项调用 `tool()` 工厂或重复 `add`。`input` 必须与成员输入类型匹配,成功映射参数从成员输出推导为 `Stable<Output>`。提前定义登记项时可用 `satisfies ToolMember<typeof lookup>` 保持类型检查。
32
+
33
+ 参数 Schema 只校验,不做默认填充、转换或移除字段。非法 JSON、未知工具和 Schema 不匹配形成明确错误反馈,不创建工具子调用。Schema 自身故障及改变参数的解析结果会终止推进。工具的普通业务失败只有提供 `result.failed` 才转成反馈;取消、CP、资源持久化等受保护故障不进入业务失败映射。映射必须纯、无副作用;允许异步,但恢复可能重新执行尚未保存接纳的映射。
34
+
35
+ 构造时固定登记和配置,一棵结构内每个成员实例唯一。`policyVersion` 由作者维护,覆盖 JSON Schema 无法表示的细化判断和映射行为。目录摘要还包含 Schema、成员顺序/种类/CP 契约及预算。Zod 升级可能改变摘要并拒绝原 CP 恢复。
36
+
37
+ ## 普通资源登记
38
+
39
+ ```ts
40
+ import { Resource } from '@weiliang0121/capix-resources';
41
+ import { Resources, Runner } from '@weiliang0121/capix-runtime';
42
+ import { Interactions } from '@weiliang0121/capix-interaction';
43
+ import { requireModelRequest, requireModelReply } from '@weiliang0121/capix-model-provider';
44
+ import { ModelExchanges, ModelExchangeResource } from '@weiliang0121/capix-model-exchanges';
45
+ import { ToolModelResource } from '@weiliang0121/capix-tool';
46
+
47
+ // provider 是宿主配置的 ModelProvider,例如 ChatCompletionsProvider。
48
+ const contract = { key: 'app.model.v1', request: requireModelRequest, reply: requireModelReply };
49
+ const interactions = new Interactions().register(contract, { send: (request) => provider.complete(request) });
50
+ const exchanges = new ModelExchanges();
51
+ const resources = new Resources().register(new Resource(exchanges), ModelExchangeResource).register(new Resource({ exchanges, channel: interactions.bind(contract) }), ToolModelResource);
52
+ const runner = new Runner(scene, resources, interactions).build();
53
+ const text = await runner.run({ request: { messages: [{ role: 'user', content: 'Translate hello.' }] } });
54
+ ```
55
+
56
+ 交换资源管理固定内容;模型资源使用该实体重建请求,经独立 Interaction 发送/等待。两次登记必须使用同一个实体。没有父输入 capture、专用 installer 或隐藏的按次供给对象。每次 Tool 调用创建独立起点,多个调用可共享实体而不混用交换顺序;共享实体的存量上限仍共同计费。
57
+
58
+ 输入 `{ request }` 保留初始消息、toolChoice、maxTokens、responseFormat、temperature、stream;不能替换 `tools` 或 `exchanges`。每次模型子调用仅接收 `{ origin, exchanges }` 引用,发送资源才展开累计请求。`stream` 可供 Provider 读取 SSE,但本预设只交付最终正文,不转交候选模型正文。`stages` 提供内部模型与工具的结构 ID,供宿主按真实观测事件投影进度。
59
+
60
+ 离线例子:`pnpm build && node examples/tool.mjs`,输出 `你好`。完整代码见 [examples/tool.mjs](../../examples/tool.mjs)。
61
+
62
+ ## 预算与等待
63
+
64
+ 工具串行执行,1–32 个登记成员;`maxModelCalls` 默认 1000,接受正安全整数;`waitMs` 默认 30000。调用次数用于限制单次 Tool 调用的持续推进,作为异常循环的兜底,作者也可按业务要求显式设置更小或更大的值。次数包含最后一次生成回答的模型请求;一次请求可以提出多个工具调用,因此不等于工具执行次数。
65
+
66
+ 已达到 `maxModelCalls` 时,不再启动下一次模型子调用,并抛出公开的 `ToolModelCallLimitError`(`code: 'TOOL_MODEL_CALL_LIMIT'`,`calls` 为已启动的模型调用数,`limit` 为配置上限)。最后一次允许的请求若给出最终回答,可正常完成;若提出工具,则完成这些工具的处理,再在下一次模型请求前检查次数。已有工具效果不会撤销。宿主可据错误类型明确展示次数耗尽,不应将其当作模型服务故障或自动重试。
67
+
68
+ 次数按同一调用累计,显式恢复不会重置。它仍属于目录的固定配置:上限改变会拒绝原 CP;此前省略配置的调用使用默认 8,与现在的默认 1000 不同。提高次数不扩大上下文、实体、持久材料或时间容量,也不提供累计 token 额度;需要 token 或费用策略时由宿主独立计量。下列容量按完整 JSON 的 UTF-8 字节计量。
69
+
70
+ | 配置 | 默认值 | 检查位置 |
71
+ | ----------------------------- | ------ | --------------------------------------------------- |
72
+ | `budgets.maxReplyBytes` | 64 KiB | 模型回复接纳及恢复 |
73
+ | `budgets.maxFeedbackBytes` | 4 KiB | 包含 callId 的反馈;派发前保守预留 |
74
+ | `budgets.maxRequestBytes` | 2 MiB | 展开请求、发送前及工具派发前 |
75
+ | `budgets.maxWireRequestBytes` | 不设置 | 显式设置时必须登记 inspection,按实际 wire 编码检查 |
76
+ | `budgets.maxCollectionBytes` | 8 MiB | 当前实体所有起点、交换与待写入预留 |
77
+ | `tools[name].maxOutputBytes` | 不设置 | 成员返回后核对;成员应在自身 CP 前主动限制输出 |
78
+
79
+ `ToolModelSource.inspection` 可直接使用提供同步 `inspectRequest` 的 Provider。显式 wire 限制但缺少测量能力会拒绝发送。集合预算是预设的保守检查,实体和 Provider 的独立上限仍然成立;共享并发调用没有跨实体预留事务。集合核算与累计请求构造目前读取完整材料,不宣称常数成本,也不自动删除历史。
80
+
81
+ `ToolModelPending` 携带原交互引用。局部等待超时或取消不会重发、取消原模型请求;超时后的失败调用不会因为引用仍存在就自动变成可恢复调用。宿主关闭时须等待自己持有的独立发送完成。
82
+
83
+ ## 显式恢复
84
+
85
+ 默认不开 CP。`checkpoint: true` 为 Tool 和内部模型节点声明恢复点,并要求所有工具成员已有 CP 契约;还需宿主提供 CP Store、持久 ModelExchanges、Interaction 持久化及 `ToolModelSource.recovery.read(id)` 原始交互查询。`new Scene(answer)` 本身不自动创建 Scene 的恢复契约;从根恢复时,上层仍需显式保存准确子调用引用并声明恢复入口。
86
+
87
+ | 保存边界 | 恢复依据 |
88
+ | ------------- | -------------------------------------------------- |
89
+ | prepared | 固定起点 ID 与目录摘要先保存;随后幂等建立相同起点 |
90
+ | model-waiting | 准确模型子调用引用;原交互请求与已保存反馈 |
91
+ | tools | 当前交换、游标及派发前保存的准确工具引用 |
92
+ | completed | 最终回复、全部接纳依据与原交互;不再执行模型或工具 |
93
+
94
+ 恢复重新核对输入、目录、交换前缀、模型回复、工具返回/接纳与效果凭据。内部模型交付保留 `{ reference, reply }`,父层已接纳后也能确认原交互。缺失原交互/反馈/起点/必要子 CP、目录变化或失败终态都会拒绝,不尝试重发探测。
95
+
96
+ CP 使用有序增量选择登记子引用;预设的 point.check 仍检查完整引用关系,交换前缀 state 仍完整编码。它不把已有的内核增量保存保证扩大成整个模型循环的恒定成本承诺。
97
+
98
+ 工具自身的外部效果确认仍归其资源和业务契约;多个实体提交与父 CP 不构成统一事务。当前格式与契约变化拒绝旧依据,没有旧 ToolLoop 别名或数据升级路径。
99
+
100
+ ## 验证与维护
101
+
102
+ `pnpm --filter @weiliang0121/capix-tool test` 独立构建依赖并运行本包测试,覆盖普通/CP 调用、错误分流、预算、取消、共享实体、多步推进、类型负例,以及新 Runner/Store 的恢复和缺失依据拒绝。夹具只使用公开包,验证不依赖外部服务或应用测试。
103
+
104
+ MIT. Copyright (c) 2026 wei.liang. See [LICENSE](LICENSE).
@@ -0,0 +1,27 @@
1
+ import { type ModelExchangeEntry, type ModelExchangeOrigin } from '@weiliang0121/capix-model-exchanges';
2
+ import { type ModelReply, type ModelRequest, type ModelRequestInspection, type ToolResult } from '@weiliang0121/capix-model-provider';
3
+ export interface ToolByteBudgets {
4
+ readonly maxReplyBytes: number;
5
+ readonly maxFeedbackBytes: number;
6
+ readonly maxRequestBytes: number;
7
+ readonly maxWireRequestBytes?: number;
8
+ readonly maxCollectionBytes: number;
9
+ }
10
+ export declare const defaultToolByteBudgets: ToolByteBudgets;
11
+ export interface ToolRequestInspection {
12
+ inspectRequest(request: ModelRequest): ModelRequestInspection;
13
+ }
14
+ export declare function resolveToolBudgets(configuration?: Partial<ToolByteBudgets>): ToolByteBudgets;
15
+ export declare class ToolBudgetError extends Error {
16
+ readonly code: 'TOOL_REPLY_LIMIT' | 'TOOL_FEEDBACK_LIMIT' | 'TOOL_OUTPUT_LIMIT' | 'TOOL_REQUEST_LIMIT' | 'TOOL_WIRE_REQUEST_LIMIT' | 'TOOL_COLLECTION_LIMIT';
17
+ readonly bytes: number;
18
+ readonly limit: number;
19
+ constructor(code: 'TOOL_REPLY_LIMIT' | 'TOOL_FEEDBACK_LIMIT' | 'TOOL_OUTPUT_LIMIT' | 'TOOL_REQUEST_LIMIT' | 'TOOL_WIRE_REQUEST_LIMIT' | 'TOOL_COLLECTION_LIMIT', bytes: number, limit: number);
20
+ }
21
+ export declare function assertToolReplyBytes(reply: ModelReply, budgets: Pick<ToolByteBudgets, 'maxReplyBytes'>): ModelReply;
22
+ export declare function assertToolFeedbackBytes(toolCallId: string, result: ToolResult, budgets: ToolByteBudgets): ToolResult;
23
+ export declare function assertToolOutputBytes<Value>(value: Value, limit: number): Value;
24
+ export declare function assertToolRequestBytes(request: ModelRequest, budgets: ToolByteBudgets, inspection?: ToolRequestInspection, growth?: number): void;
25
+ export declare function assertToolCollectionBytes(entries: readonly ModelExchangeEntry[], budgets: ToolByteBudgets, growth?: number, origins?: readonly ModelExchangeOrigin[]): void;
26
+ export declare function reserveToolReply(entries: readonly ModelExchangeEntry[], budgets: ToolByteBudgets, origins?: readonly ModelExchangeOrigin[]): void;
27
+ export declare function reserveToolFeedback(entries: readonly ModelExchangeEntry[], current: ModelExchangeEntry, pending: number, budgets: ToolByteBudgets, origins?: readonly ModelExchangeOrigin[]): void;
package/dist/budget.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{types}from"node:util";import{maximumModelExchangeEntryBytes}from"@weiliang0121/capix-model-exchanges";import{requireModelReply,requireModelRequest,requireToolFeedback,requireToolResult}from"@weiliang0121/capix-model-provider";const defaultToolByteBudgets=Object.freeze({maxReplyBytes:64*1024,maxFeedbackBytes:4*1024,maxRequestBytes:2*1024*1024,maxCollectionBytes:8*1024*1024});function resolveToolBudgets(configuration={}){const resolved={...defaultToolByteBudgets,...configuration};for(const key of Object.keys(resolved)){if(key!=="maxWireRequestBytes"&&!Object.hasOwn(defaultToolByteBudgets,key))throw new TypeError(`Unknown tool byte budget: ${key}`);const value=resolved[key];if(key==="maxWireRequestBytes"&&value===void 0)continue;const ceiling=key==="maxCollectionBytes"?128*1024*1024:2*1024*1024;if(typeof value!="number"||!Number.isSafeInteger(value)||value<1||value>ceiling)throw new RangeError(`Invalid tool byte budget: ${key}`)}return resolved.maxWireRequestBytes===void 0&&delete resolved.maxWireRequestBytes,Object.freeze(resolved)}__name(resolveToolBudgets,"resolveToolBudgets");class ToolBudgetError extends Error{static{__name(this,"ToolBudgetError")}code;bytes;limit;constructor(code,bytes2,limit){super(`${code}: ${bytes2} bytes exceeds ${limit}`),this.code=code,this.bytes=bytes2,this.limit=limit,this.name="ToolBudgetError"}}function bytes(value){return Buffer.byteLength(JSON.stringify(value),"utf8")}__name(bytes,"bytes");function within(code,value,limit){if(value>limit)throw new ToolBudgetError(code,value,limit)}__name(within,"within");function assertToolReplyBytes(reply,budgets){return requireModelReply(reply),within("TOOL_REPLY_LIMIT",bytes(reply),budgets.maxReplyBytes),reply}__name(assertToolReplyBytes,"assertToolReplyBytes");function assertToolFeedbackBytes(toolCallId,result,budgets){if(typeof toolCallId!="string"||!toolCallId.trim()||toolCallId.length>512)throw new TypeError("Tool feedback identity cannot be stored in its exchange");requireToolResult(result);const feedback=requireToolFeedback({toolCallId,...result});return within("TOOL_FEEDBACK_LIMIT",bytes(feedback),budgets.maxFeedbackBytes),result}__name(assertToolFeedbackBytes,"assertToolFeedbackBytes");function assertToolOutputBytes(value,limit){if(!Number.isSafeInteger(limit)||limit<1||limit>8*1024*1024)throw new RangeError("Invalid tool output byte limit");const ancestors=new Set;let total=0,visited=0;const add=__name(amount=>{total+=amount,within("TOOL_OUTPUT_LIMIT",total,limit)},"add"),visit=__name((item,depth)=>{if(++visited>1e5||depth>64)throw new TypeError("Tool output exceeds JSON structure limits");if(item===null||typeof item=="boolean"||typeof item=="number"&&Number.isFinite(item))return add(bytes(item));if(typeof item=="string")return within("TOOL_OUTPUT_LIMIT",total+Buffer.byteLength(item),limit),add(bytes(item));if(!item||typeof item!="object"||types.isProxy(item)||ancestors.has(item))throw new TypeError("Tool output must be finite acyclic JSON");const array=Array.isArray(item);if(!array&&Object.getPrototypeOf(item)!==Object.prototype&&Object.getPrototypeOf(item)!==null)throw new TypeError("Tool output must contain plain JSON objects");const descriptors=Object.getOwnPropertyDescriptors(item),keys=Reflect.ownKeys(descriptors);if(array&&keys.length!==item.length+1)throw new TypeError("Tool output arrays must be dense");const count=keys.length-(array?1:0);add(2+Math.max(0,count-1)),ancestors.add(item);for(const key of keys){if(array&&key==="length")continue;if(typeof key!="string")throw new TypeError("Tool output cannot contain symbols");const descriptor=descriptors[key];if(!descriptor.enumerable||!("value"in descriptor))throw new TypeError("Tool output cannot contain accessors or hidden fields");if(array&&(!/^(0|[1-9][0-9]*)$/u.test(key)||Number(key)>=item.length))throw new TypeError("Tool output arrays cannot have extra fields");array||add(bytes(key)+1),visit(descriptor.value,depth+1)}ancestors.delete(item)},"visit");return visit(value,0),value}__name(assertToolOutputBytes,"assertToolOutputBytes");function assertToolRequestBytes(request,budgets,inspection,growth=0){if(requireModelRequest(request),budgets.maxWireRequestBytes!==void 0&&!inspection)throw new TypeError("An explicit tool wire request budget requires host request inspection");const measured=inspection?.inspectRequest(request);if(measured&&typeof measured=="object"&&"then"in measured)throw Promise.resolve(measured).catch(()=>{}),new TypeError("Tool request inspection must be synchronous");if(inspection&&(!measured||typeof measured!="object"||!Number.isSafeInteger(measured.requestBytes)||measured.requestBytes<0||!Number.isSafeInteger(measured.wireBytes)||measured.wireBytes<0))throw new TypeError("Tool request inspection must synchronously return finite byte counts");if(!Number.isSafeInteger(growth)||growth<0)throw new RangeError("Invalid tool request byte reservation");within("TOOL_REQUEST_LIMIT",Math.max(measured?.requestBytes??0,bytes(request))+growth,budgets.maxRequestBytes),measured&&budgets.maxWireRequestBytes!==void 0&&within("TOOL_WIRE_REQUEST_LIMIT",measured.wireBytes+2*growth,budgets.maxWireRequestBytes)}__name(assertToolRequestBytes,"assertToolRequestBytes");const newEntryEnvelopeBytes=20*1024,feedbackReceiptEnvelopeBytes=16*1024;function assertToolCollectionBytes(entries,budgets,growth=0,origins=[]){within("TOOL_COLLECTION_LIMIT",entries.reduce((total,entry)=>total+bytes(entry),0)+origins.reduce((total,origin)=>total+bytes(origin),0)+growth,budgets.maxCollectionBytes)}__name(assertToolCollectionBytes,"assertToolCollectionBytes");function reserveToolReply(entries,budgets,origins=[]){const growth=budgets.maxReplyBytes+newEntryEnvelopeBytes;within("TOOL_COLLECTION_LIMIT",growth,maximumModelExchangeEntryBytes),assertToolCollectionBytes(entries,budgets,growth,origins)}__name(reserveToolReply,"reserveToolReply");function reserveToolFeedback(entries,current,pending,budgets,origins=[]){const growth=pending*(budgets.maxFeedbackBytes+feedbackReceiptEnvelopeBytes);within("TOOL_COLLECTION_LIMIT",bytes(current)+growth,maximumModelExchangeEntryBytes),assertToolCollectionBytes(entries,budgets,growth,origins)}__name(reserveToolFeedback,"reserveToolFeedback");export{ToolBudgetError,assertToolCollectionBytes,assertToolFeedbackBytes,assertToolOutputBytes,assertToolReplyBytes,assertToolRequestBytes,defaultToolByteBudgets,reserveToolFeedback,reserveToolReply,resolveToolBudgets};
@@ -0,0 +1,35 @@
1
+ import { type ChildCallable } from '@weiliang0121/capix-structure';
2
+ import { type ToolResult, type ModelTool } from '@weiliang0121/capix-model-provider';
3
+ import type { ToolMember } from './types.js';
4
+ import type { ToolByteBudgets } from './budget.js';
5
+ export interface ToolEntry {
6
+ readonly name: string;
7
+ readonly position: number;
8
+ readonly registration: ToolMember<ChildCallable>;
9
+ }
10
+ export interface ToolCatalog {
11
+ readonly tools: readonly ModelTool[];
12
+ readonly digest: string;
13
+ readonly policyVersion: string;
14
+ }
15
+ export declare function toolEntries(value: unknown, checkpoint: boolean): readonly ToolEntry[];
16
+ export declare function createToolCatalog(entries: readonly ToolEntry[], policyVersion: string, execution: {
17
+ readonly maxModelCalls: number;
18
+ readonly waitMs: number;
19
+ readonly budgets: ToolByteBudgets;
20
+ }): ToolCatalog;
21
+ export type ToolSelection = {
22
+ readonly kind: 'accepted';
23
+ readonly entry: ToolEntry;
24
+ readonly input: unknown;
25
+ } | {
26
+ readonly kind: 'rejected';
27
+ readonly result: ToolResult;
28
+ };
29
+ export declare function selectTool(entries: readonly ToolEntry[], call: {
30
+ readonly name: string;
31
+ readonly arguments: {
32
+ readonly encoding: 'json-text';
33
+ readonly text: string;
34
+ };
35
+ }): ToolSelection;
@@ -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{isDeepStrictEqual,types}from"node:util";import{z}from"zod";import{cloneCheckpointValue}from"@weiliang0121/capix-checkpoint";import{Callable}from"@weiliang0121/capix-structure";import{requireModelRequest}from"@weiliang0121/capix-model-provider";function fields(value){if(!value||typeof value!="object"||Array.isArray(value)||types.isProxy(value)||Object.getOwnPropertySymbols(value).length)throw new TypeError("Tool configuration must contain data properties");const result=Object.create(null);for(const[key,descriptor]of Object.entries(Object.getOwnPropertyDescriptors(value))){if(!descriptor.enumerable||!("value"in descriptor))throw new TypeError("Tool configuration must contain enumerable data properties");result[key]=descriptor.value}return result}__name(fields,"fields");function toolEntries(value,checkpoint){const registrations=fields(value),entries=[],seen=new Set;for(const[name,configuration]of Object.entries(registrations)){if(!/^[A-Za-z_][A-Za-z0-9_]{0,63}$/u.test(name)||["models","completion","__proto__","constructor","prototype"].includes(name))throw new TypeError("Invalid tool member name");const item=fields(configuration);if(Object.keys(item).some(key=>!["node","description","input","maxOutputBytes","result"].includes(key)))throw new TypeError("Unknown tool member configuration");const node=item.node;if(types.isProxy(node)||!(node instanceof Callable)||node.kind!=="node"&&node.kind!=="group")throw new TypeError("A tool member must be a Node or Group");if(seen.has(node))throw new TypeError("Tool members require distinct structure identities");if(checkpoint&&!node.checkpoint)throw new TypeError("Recoverable Tool requires checkpoint contracts on its members");if(typeof item.description!="string"||!item.description.trim())throw new TypeError("A tool member requires a description");const input=item.input;if(!input||types.isProxy(input)||typeof input.parse!="function")throw new TypeError("A tool member requires an input schema");const result=fields(item.result);if(Object.keys(result).some(key=>key!=="succeeded"&&key!=="failed")||typeof result.succeeded!="function"||result.failed!==void 0&&typeof result.failed!="function")throw new TypeError("A tool member requires a result mapping");const maxOutputBytes=item.maxOutputBytes;if(maxOutputBytes!==void 0&&(typeof maxOutputBytes!="number"||!Number.isSafeInteger(maxOutputBytes)||maxOutputBytes<1||maxOutputBytes>8*1024*1024))throw new RangeError("Invalid tool output byte limit");const registration=Object.freeze({node,description:item.description,input,...maxOutputBytes===void 0?{}:{maxOutputBytes},result:Object.freeze({succeeded:result.succeeded.bind(item.result),...result.failed?{failed:result.failed.bind(item.result)}:{}})});entries.push(Object.freeze({name,position:entries.length+1,registration})),seen.add(node)}if(!entries.length||entries.length>32)throw new RangeError("Tool requires between 1 and 32 members");return Object.freeze(entries)}__name(toolEntries,"toolEntries");function canonical(value){return Object.is(value,-0)?"-0":value===null||typeof value!="object"?JSON.stringify(value):Array.isArray(value)?`[${value.map(canonical).join(",")}]`:`{${Object.keys(value).sort().map(key=>`${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`}__name(canonical,"canonical");function freeze(value){if(value&&typeof value=="object"){for(const child of Object.values(value))freeze(child);Object.freeze(value)}return value}__name(freeze,"freeze");function createToolCatalog(entries,policyVersion,execution){if(typeof policyVersion!="string"||!policyVersion.trim()||policyVersion.length>512)throw new TypeError("Tool requires an explicit policyVersion");const tools=entries.map(({name,registration})=>{const{$schema,...parameters}=z.toJSONSchema(registration.input,{target:"draft-7"}),schema=z.record(z.string(),z.json()).parse(parameters);return freeze({name,description:registration.description,parameters:schema})});requireModelRequest({messages:[{role:"user",content:""}],tools});const basis=cloneCheckpointValue({formatVersion:1,policyVersion,precheckVersion:1,execution,tools,members:entries.map(({name,position,registration})=>({name,position,kind:registration.node.kind,maxOutputBytes:registration.maxOutputBytes??null,checkpoint:registration.node.checkpoint?{key:registration.node.checkpoint.key,version:registration.node.checkpoint.version}:null}))});return Object.freeze({policyVersion,tools:Object.freeze(tools),digest:createHash("sha256").update(canonical(basis)).digest("hex")})}__name(createToolCatalog,"createToolCatalog");function selectTool(entries,call){const entry=entries.find(item=>item.name===call.name);if(!entry)return reject("UNKNOWN_TOOL","Requested tool is not registered.");let input;try{input=JSON.parse(call.arguments.text)}catch{return reject("INVALID_ARGUMENTS","Tool arguments must be valid JSON.")}try{const captured=cloneCheckpointValue(input),parsed=entry.registration.input.parse(captured);if(!isDeepStrictEqual(parsed,captured))throw new TypeError("Tool input schema must preserve the fixed model arguments");return{kind:"accepted",entry,input:parsed}}catch(error){if(!(error instanceof z.ZodError))throw error;return reject("INVALID_ARGUMENTS","Tool arguments do not match the registered schema.")}}__name(selectTool,"selectTool");function reject(code,message){return{kind:"rejected",result:{status:"error",content:[{kind:"text",text:JSON.stringify({code,message})}]}}}__name(reject,"reject");export{createToolCatalog,selectTool,toolEntries};
@@ -0,0 +1,4 @@
1
+ import type { CheckpointCodec } from '@weiliang0121/capix-structure';
2
+ import { z } from 'zod';
3
+ export declare function codec<Value>(schema: z.ZodType<Value>): CheckpointCodec<Value>;
4
+ export declare const stringCodec: CheckpointCodec<string>;
package/dist/codec.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{cloneCheckpointValue}from"@weiliang0121/capix-checkpoint";import{z}from"zod";function codec(schema){return{encode:__name(value=>cloneCheckpointValue(schema.parse(value)),"encode"),decode:__name(value=>schema.parse(value),"decode")}}__name(codec,"codec");const stringCodec=codec(z.string());export{codec,stringCodec};
@@ -0,0 +1,5 @@
1
+ export { Tool, ToolModelCallLimitError } from './tool.js';
2
+ export type { ToolMember, ToolMembers, ToolConfiguration, ToolInput, ToolBaseRequest } from './types.js';
3
+ export type { ToolCatalog } from './catalog.js';
4
+ export { ToolModelResource, ToolModelPending, type ToolModelSource, type ToolInteractionRecovery } from './model.js';
5
+ export { ToolBudgetError, defaultToolByteBudgets, assertToolOutputBytes, type ToolByteBudgets, type ToolRequestInspection } from './budget.js';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ /*! MIT License | Copyright (c) 2026 wei.liang | See LICENSE */
2
+ import{Tool,ToolModelCallLimitError}from"./tool.js";import{ToolModelResource,ToolModelPending}from"./model.js";import{ToolBudgetError,defaultToolByteBudgets,assertToolOutputBytes}from"./budget.js";export{Tool,ToolBudgetError,ToolModelCallLimitError,ToolModelPending,ToolModelResource,assertToolOutputBytes,defaultToolByteBudgets};
@@ -0,0 +1,78 @@
1
+ import { type InteractionChannel, type InteractionReference, type InteractionState } from '@weiliang0121/capix-interaction';
2
+ import type { ModelReply, ModelRequest } from '@weiliang0121/capix-model-provider';
3
+ import { type ModelExchanges, type ExchangeReference, type ExchangePrefix } from '@weiliang0121/capix-model-exchanges';
4
+ import { ResourceOffer, type ResourceOperation, type ResourcesOf } from '@weiliang0121/capix-resources';
5
+ import { Node } from '@weiliang0121/capix-structure';
6
+ import type { ToolCatalog } from './catalog.js';
7
+ import { type ToolByteBudgets, type ToolRequestInspection } from './budget.js';
8
+ export interface ToolModelSource {
9
+ readonly channel: InteractionChannel<ModelRequest, ModelReply, string>;
10
+ readonly exchanges: ModelExchanges;
11
+ readonly recovery?: ToolInteractionRecovery;
12
+ readonly inspection?: ToolRequestInspection;
13
+ }
14
+ export interface ToolInteractionRecovery {
15
+ read(id: string): InteractionState<ModelRequest, ModelReply> | undefined;
16
+ }
17
+ export declare class ToolModelPending extends Error {
18
+ readonly reference: InteractionReference;
19
+ constructor(reference: InteractionReference);
20
+ }
21
+ export declare const ToolModelResource: ResourceOffer<ToolModelSource, import("@weiliang0121/capix-resources").ResourceMethods<{
22
+ readonly start: (source: ToolModelSource, input: {
23
+ readonly prefix: ExchangePrefix;
24
+ readonly budgets: ToolByteBudgets;
25
+ }) => InteractionReference;
26
+ readonly preflight: (source: ToolModelSource, input: ExchangePrefix & {
27
+ readonly current: ExchangeReference;
28
+ readonly budgets: ToolByteBudgets;
29
+ }) => void;
30
+ readonly confirm: (source: ToolModelSource, input: {
31
+ readonly reference: InteractionReference;
32
+ readonly basis: ExchangePrefix;
33
+ readonly reply?: ModelReply;
34
+ readonly budgets: ToolByteBudgets;
35
+ }) => void;
36
+ readonly wait: (source: ToolModelSource, input: {
37
+ readonly reference: InteractionReference;
38
+ readonly waitMs: number;
39
+ readonly maxReplyBytes: number;
40
+ }, operation: ResourceOperation) => Promise<ModelReply>;
41
+ }>, ToolModelSource, never>;
42
+ export interface ToolCompletion {
43
+ readonly reference: InteractionReference;
44
+ readonly reply: ModelReply;
45
+ }
46
+ export declare function createToolCompletion(checkpoint: boolean, catalog: ToolCatalog, budgets: ToolByteBudgets, waitMs: number): Node<ExchangePrefix, ToolCompletion, ResourcesOf<{
47
+ model: import("@weiliang0121/capix-resources").ResourceRequest<import("@weiliang0121/capix-resources").ResourceMethods<{
48
+ readonly start: (source: ToolModelSource, input: {
49
+ readonly prefix: ExchangePrefix;
50
+ readonly budgets: ToolByteBudgets;
51
+ }) => InteractionReference;
52
+ readonly preflight: (source: ToolModelSource, input: ExchangePrefix & {
53
+ readonly current: ExchangeReference;
54
+ readonly budgets: ToolByteBudgets;
55
+ }) => void;
56
+ readonly confirm: (source: ToolModelSource, input: {
57
+ readonly reference: InteractionReference;
58
+ readonly basis: ExchangePrefix;
59
+ readonly reply?: ModelReply;
60
+ readonly budgets: ToolByteBudgets;
61
+ }) => void;
62
+ readonly wait: (source: ToolModelSource, input: {
63
+ readonly reference: InteractionReference;
64
+ readonly waitMs: number;
65
+ readonly maxReplyBytes: number;
66
+ }, operation: ResourceOperation) => Promise<ModelReply>;
67
+ }>>;
68
+ exchanges: import("@weiliang0121/capix-resources").ResourceRequest<Readonly<{
69
+ begin(id: string, basis: import("@weiliang0121/capix-model-exchanges").ModelExchangeBasis): import("@weiliang0121/capix-model-exchanges").ExchangeOriginReference;
70
+ origin(reference: import("@weiliang0121/capix-model-exchanges").ExchangeOriginReference): import("@weiliang0121/capix-model-exchanges").ModelExchangeOrigin;
71
+ request(prefix: ExchangePrefix): import("@weiliang0121/capix-model-provider").ModelRequest;
72
+ intake(origin: import("@weiliang0121/capix-model-exchanges").ExchangeOriginReference, reply: ModelReply): import("@weiliang0121/capix-model-exchanges").ExchangeIntakeReceipt;
73
+ read(origin: import("@weiliang0121/capix-model-exchanges").ExchangeOriginReference, reference: ExchangeReference, exact?: boolean): import("@weiliang0121/capix-model-exchanges").ModelExchangeEntry;
74
+ closed(origin: import("@weiliang0121/capix-model-exchanges").ExchangeOriginReference, references: readonly ExchangeReference[]): readonly import("@weiliang0121/capix-model-exchanges").ModelExchangeEntry[];
75
+ receive(origin: import("@weiliang0121/capix-model-exchanges").ExchangeOriginReference, reference: ExchangeReference, callId: string, result: import("@weiliang0121/capix-model-provider").ToolResult): import("@weiliang0121/capix-model-exchanges").ExchangeFeedbackReceipt;
76
+ complete(origin: import("@weiliang0121/capix-model-exchanges").ExchangeOriginReference, reference: ExchangeReference, callId: string, result: import("@weiliang0121/capix-model-provider").ToolResult): ExchangeReference;
77
+ }>>;
78
+ }>, never>;
package/dist/model.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{Interaction}from"@weiliang0121/capix-interaction";import{requireModelReply,requireModelRequest}from"@weiliang0121/capix-model-provider";import{ModelExchangeResource}from"@weiliang0121/capix-model-exchanges";import{RuntimeError}from"@weiliang0121/capix-contracts";import{PersistenceError}from"@weiliang0121/capix-diagnostics";import{isDeepStrictEqual}from"node:util";import{ResourceOffer,ResourcePortError}from"@weiliang0121/capix-resources";import{Node,checkpointPoint}from"@weiliang0121/capix-structure";import{cloneCheckpointValue}from"@weiliang0121/capix-checkpoint";import{z}from"zod";import{codec}from"./codec.js";import{assertToolReplyBytes,assertToolFeedbackBytes,assertToolRequestBytes,reserveToolReply,reserveToolFeedback}from"./budget.js";class ToolModelPending extends Error{static{__name(this,"ToolModelPending")}reference;constructor(reference){super("Model reply is still pending; read the same interaction later"),this.name="ToolModelPending",this.reference=Object.freeze({id:reference.id,contract:reference.contract})}}function request(source,prefix,budgets){const value=source.exchanges.request(prefix);return assertToolRequestBytes(value,budgets,source.inspection),value}__name(request,"request");const ToolModelResource=new ResourceOffer({name:"tool.model",methods:{start(source,input){const value=request(source,input.prefix,input.budgets),snapshot=source.exchanges.snapshot();return reserveToolReply(snapshot.entries,input.budgets,snapshot.origins),source.channel.start(value)},preflight(source,input){const base=request(source,{origin:input.origin,exchanges:input.exchanges},input.budgets),current=source.exchanges.read(input.origin,input.current,!1);if(assertToolReplyBytes(current.reply,input.budgets),source.exchanges.values(input.origin)[input.exchanges.length]?.id!==current.id)throw new Error("Tool exchange is not the next exchange in its origin");const pending=current.reply.output.filter(item=>item.kind==="tool-call").filter(call=>!current.feedback.some(item=>item.toolCallId===call.id)),placeholders=pending.map(call=>{const content=[{kind:"text",text:""}],result=assertToolFeedbackBytes(call.id,{status:"ok",content},input.budgets);for(let count=1;count<32;count++){const candidate=[...content,{kind:"text",text:""}];if(Buffer.byteLength(JSON.stringify({toolCallId:call.id,status:"ok",content:candidate}))>input.budgets.maxFeedbackBytes)break;content.push({kind:"text",text:""})}return{toolCallId:call.id,...result}});for(const feedback of current.feedback)assertToolFeedbackBytes(feedback.toolCallId,{status:feedback.status,content:feedback.content},input.budgets);const snapshot=source.exchanges.snapshot();reserveToolFeedback(snapshot.entries,current,pending.length,input.budgets,snapshot.origins);const estimate={...base,exchanges:[...base.exchanges??[],{reply:current.reply,feedback:[...current.feedback,...placeholders]}]};assertToolRequestBytes(estimate,input.budgets,source.inspection,pending.length*input.budgets.maxFeedbackBytes)},confirm(source,input){try{const saved=source.recovery?.read(input.reference.id);if(saved&&typeof saved=="object"&&"then"in saved)throw Promise.resolve(saved).catch(()=>{}),new TypeError("Tool interaction recovery read must be synchronous");if(!saved)throw new Error("Original model request or durable feedback is unavailable");const interaction=Interaction.restore(saved,{contract:{key:input.reference.contract,request:requireModelRequest,reply:requireModelReply}}),state=interaction.exportState(),received=interaction.read();if(state.id!==input.reference.id||received.kind!=="received"||!isDeepStrictEqual(state.request,request(source,input.basis,input.budgets)))throw new Error("Original model request or durable feedback is unavailable");if(assertToolReplyBytes(received.feedback.payload,input.budgets),input.reply!==void 0&&!isDeepStrictEqual(input.reply,received.feedback.payload))throw new Error("Completed model reply differs from its durable interaction")}catch(cause){throw cause instanceof PersistenceError?cause:new ResourcePortError("Tool recovery requires exact original model request and durable feedback; it will not resend",{cause:new RuntimeError("CHECKPOINT_CONTENT_CHANGED","Saved model interaction does not match the fixed request",void 0,{cause})})}},async wait(source,input,operation){if(!Number.isSafeInteger(input.waitMs)||input.waitMs<0||input.waitMs>2147483647)throw new RangeError("Model reply wait must be a nonnegative timer duration");const local=new AbortController,cancel=__name(()=>local.abort(operation.signal?.reason),"cancel"),detach=operation.onClose(reason=>local.abort(reason));operation.signal?.addEventListener("abort",cancel,{once:!0}),operation.signal?.aborted&&cancel();try{const result=await source.channel.wait(input.reference,{waitMs:input.waitMs,signal:local.signal});if(result.kind==="unavailable")throw result.error;if(result.kind!=="received")throw new ToolModelPending(input.reference);return assertToolReplyBytes(result.feedback.payload,{maxReplyBytes:input.maxReplyBytes})}finally{detach(),operation.signal?.removeEventListener("abort",cancel)}}}}),interactionReferenceSchema=z.strictObject({id:z.string().min(1),contract:z.string().min(1)}).readonly(),referenceCodec=codec(interactionReferenceSchema),referenceSchema=z.strictObject({collection:z.string().min(1),id:z.string().min(1),revision:z.number().int().nonnegative()}).readonly(),originSchema=z.strictObject({collection:z.string().min(1),id:z.string().min(1),digest:z.string().min(1)}).readonly(),prefixCodec=codec(z.strictObject({origin:originSchema,exchanges:z.array(referenceSchema).readonly()}).readonly());function createToolCompletion(checkpoint,catalog,budgets,waitMs){const toolReplyCodec={encode:__name(value=>cloneCheckpointValue(assertToolReplyBytes(value,budgets)),"encode"),decode:__name(value=>assertToolReplyBytes(requireModelReply(value),budgets),"decode")},completionCodec=codec(z.strictObject({reference:interactionReferenceSchema,reply:z.custom(value=>(toolReplyCodec.decode(value),!0))}).readonly()),deps={model:ToolModelResource.request(),exchanges:ModelExchangeResource.request()};function verify(work){const origin=work.resources.exchanges.origin(work.input.origin);if(origin.catalog.digest!==catalog.digest||origin.catalog.policyVersion!==catalog.policyVersion||!isDeepStrictEqual(origin.catalog.tools,catalog.tools))throw new ResourcePortError("Tool catalog differs from the fixed model origin",{cause:new RuntimeError("CHECKPOINT_CONTENT_CHANGED","Tool catalog differs from the fixed model origin")})}__name(verify,"verify");const point=checkpointPoint(),completed=point({name:"completed",state:codec(z.strictObject({reference:interactionReferenceSchema,reply:z.custom(value=>(toolReplyCodec.decode(value),!0))}).readonly()),references:{},resume:{deps,run:__name(async(work,{reference,reply})=>(verify(work),await work.resources.model.confirm({reference,basis:work.input,reply,budgets}),{reference,reply}),"run")}}),established=point({name:"established",state:referenceCodec,references:{},resume:{deps,run:__name(async(work,reference)=>(verify(work),await work.resources.model.confirm({reference,basis:work.input,budgets}),receive(work,reference)),"run")}});async function receive(work,reference){const reply=await work.resources.model.wait({reference,waitMs,maxReplyBytes:budgets.maxReplyBytes});return work.checkpoint?.({point:completed,state:{reference,reply},refs:{}}),{reference,reply}}return __name(receive,"receive"),new Node({name:"tool-model-completion",deps,...checkpoint?{checkpoint:{key:"tool.completion",version:1,input:prefixCodec,output:completionCodec,points:[established,completed]}}:{},async run(work){verify(work);const reference=await work.resources.model.start({prefix:work.input,budgets});return work.checkpoint?.({point:established,state:reference,refs:{}}),receive(work,reference)}})}__name(createToolCompletion,"createToolCompletion");export{ToolModelPending,ToolModelResource,createToolCompletion};
package/dist/tool.d.ts ADDED
@@ -0,0 +1,66 @@
1
+ import { type ModelToolCall } from '@weiliang0121/capix-model-provider';
2
+ import { type ExchangeReference, type ExchangeOriginReference } from '@weiliang0121/capix-model-exchanges';
3
+ import type { ResourcesOf } from '@weiliang0121/capix-resources';
4
+ import { Group, type ChildCallable } from '@weiliang0121/capix-structure';
5
+ import { type ToolCatalog, type ToolSelection } from './catalog.js';
6
+ import type { ToolConfiguration, ToolInput, ToolMembers } from './types.js';
7
+ import { type ToolByteBudgets } from './budget.js';
8
+ declare const deps: {
9
+ exchanges: import("@weiliang0121/capix-resources").ResourceRequest<Readonly<{
10
+ begin(id: string, basis: import("@weiliang0121/capix-model-exchanges").ModelExchangeBasis): ExchangeOriginReference;
11
+ origin(reference: ExchangeOriginReference): import("@weiliang0121/capix-model-exchanges").ModelExchangeOrigin;
12
+ request(prefix: import("@weiliang0121/capix-model-exchanges").ExchangePrefix): import("@weiliang0121/capix-model-provider").ModelRequest;
13
+ intake(origin: ExchangeOriginReference, reply: import("@weiliang0121/capix-model-provider").ModelReply): import("@weiliang0121/capix-model-exchanges").ExchangeIntakeReceipt;
14
+ read(origin: ExchangeOriginReference, reference: ExchangeReference, exact?: boolean): import("@weiliang0121/capix-model-exchanges").ModelExchangeEntry;
15
+ closed(origin: ExchangeOriginReference, references: readonly ExchangeReference[]): readonly import("@weiliang0121/capix-model-exchanges").ModelExchangeEntry[];
16
+ receive(origin: ExchangeOriginReference, reference: ExchangeReference, callId: string, result: import("@weiliang0121/capix-model-provider").ToolResult): import("@weiliang0121/capix-model-exchanges").ExchangeFeedbackReceipt;
17
+ complete(origin: ExchangeOriginReference, reference: ExchangeReference, callId: string, result: import("@weiliang0121/capix-model-provider").ToolResult): ExchangeReference;
18
+ }>>;
19
+ model: import("@weiliang0121/capix-resources").ResourceRequest<import("@weiliang0121/capix-resources").ResourceMethods<{
20
+ readonly start: (source: import("./model.js").ToolModelSource, input: {
21
+ readonly prefix: import("@weiliang0121/capix-model-exchanges").ExchangePrefix;
22
+ readonly budgets: ToolByteBudgets;
23
+ }) => import("@weiliang0121/capix-interaction").InteractionReference;
24
+ readonly preflight: (source: import("./model.js").ToolModelSource, input: import("@weiliang0121/capix-model-exchanges").ExchangePrefix & {
25
+ readonly current: ExchangeReference;
26
+ readonly budgets: ToolByteBudgets;
27
+ }) => void;
28
+ readonly confirm: (source: import("./model.js").ToolModelSource, input: {
29
+ readonly reference: import("@weiliang0121/capix-interaction").InteractionReference;
30
+ readonly basis: import("@weiliang0121/capix-model-exchanges").ExchangePrefix;
31
+ readonly reply?: import("@weiliang0121/capix-model-provider").ModelReply;
32
+ readonly budgets: ToolByteBudgets;
33
+ }) => void;
34
+ readonly wait: (source: import("./model.js").ToolModelSource, input: {
35
+ readonly reference: import("@weiliang0121/capix-interaction").InteractionReference;
36
+ readonly waitMs: number;
37
+ readonly maxReplyBytes: number;
38
+ }, operation: import("@weiliang0121/capix-resources").ResourceOperation) => Promise<import("@weiliang0121/capix-model-provider").ModelReply>;
39
+ }>>;
40
+ };
41
+ type ResourceValues = ResourcesOf<typeof deps>;
42
+ export declare class ToolModelCallLimitError extends Error {
43
+ readonly calls: number;
44
+ readonly limit: number;
45
+ readonly code = "TOOL_MODEL_CALL_LIMIT";
46
+ constructor(calls: number, limit: number);
47
+ }
48
+ export declare class Tool<const Definitions extends Readonly<Record<string, ChildCallable>>> extends Group<ToolInput, string, ResourceValues> {
49
+ readonly run: NonNullable<Group<ToolInput, string, ResourceValues>['run']>;
50
+ readonly tools: ToolMembers<Definitions>;
51
+ readonly catalog: ToolCatalog;
52
+ readonly maxModelCalls: number;
53
+ readonly waitMs: number;
54
+ readonly recoverable: boolean;
55
+ readonly budgets: ToolByteBudgets;
56
+ readonly stages: {
57
+ readonly model: string;
58
+ readonly tools: readonly {
59
+ readonly structureId: string;
60
+ readonly name: string;
61
+ }[];
62
+ };
63
+ readonly checkToolCall: (call: ModelToolCall) => ToolSelection;
64
+ constructor(configuration: ToolConfiguration<Definitions>);
65
+ }
66
+ export {};
package/dist/tool.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{randomUUID}from"node:crypto";import{types}from"node:util";import{isDeepStrictEqual as equal}from"node:util";import{z}from"zod";import{cloneCheckpointValue}from"@weiliang0121/capix-checkpoint";import{modelReplyText,requireModelRequest}from"@weiliang0121/capix-model-provider";import{ModelExchangeResource,feedbackDigest,requireExchangeIntakeReceipt,requireExchangeFeedbackReceipt}from"@weiliang0121/capix-model-exchanges";import{Group,checkpointPoint,childReferences,reception}from"@weiliang0121/capix-structure";import{codec,stringCodec}from"./codec.js";import{createToolCompletion,ToolModelResource}from"./model.js";import{createToolCatalog,selectTool,toolEntries}from"./catalog.js";import{assertToolFeedbackBytes,assertToolReplyBytes,assertToolOutputBytes,resolveToolBudgets,ToolBudgetError}from"./budget.js";const refSchema=z.strictObject({collection:z.string().min(1),id:z.string().min(1),revision:z.number().int().nonnegative()}).readonly();function inputCodecFor(catalog){return codec(z.strictObject({request:z.custom(value=>value===null||typeof value!="object"||"tools"in value||"exchanges"in value?!1:(requireModelRequest({...value,tools:catalog.tools}),!0))}).readonly())}__name(inputCodecFor,"inputCodecFor");const preparedCodec=codec(z.strictObject({id:z.string().min(1),catalog:z.string().min(1)}).readonly()),originSchema=z.strictObject({collection:z.string().min(1),id:z.string().min(1),digest:z.string().min(1)}).readonly(),deps={exchanges:ModelExchangeResource.request(),model:ToolModelResource.request()},stateCodec=codec(z.strictObject({id:z.string().min(1),catalog:z.string().min(1),origin:originSchema,exchanges:z.array(refSchema).readonly(),current:refSchema.nullable(),cursor:z.number().int().nonnegative(),active:z.strictObject({name:z.string(),index:z.number().int().nonnegative(),callId:z.string()}).readonly().nullable()}).readonly()),intakeCodec={check:requireExchangeIntakeReceipt,encode:__name(value=>cloneCheckpointValue(requireExchangeIntakeReceipt(value)),"encode"),decode:requireExchangeIntakeReceipt},feedbackCodec={check:requireExchangeFeedbackReceipt,encode:__name(value=>cloneCheckpointValue(requireExchangeFeedbackReceipt(value)),"encode"),decode:requireExchangeFeedbackReceipt};function refsAt(refs,name){const value=refs[name];if(!value)throw new Error("Tool checkpoint references an unknown member");return value}__name(refsAt,"refsAt");function referenceAt(refs,name,index){const value=refsAt(refs,name).at(index);if(!value)throw new Error("Tool checkpoint references a missing child");return value}__name(referenceAt,"referenceAt");class ToolModelCallLimitError extends Error{static{__name(this,"ToolModelCallLimitError")}calls;limit;code="TOOL_MODEL_CALL_LIMIT";constructor(calls,limit){super(`TOOL_MODEL_CALL_LIMIT: ${calls} model calls reached limit ${limit}`),this.calls=calls,this.limit=limit,this.name="ToolModelCallLimitError"}}class Tool extends Group{static{__name(this,"Tool")}tools;catalog;maxModelCalls;waitMs;recoverable;budgets;stages;checkToolCall;constructor(configuration){const options=snapshotConfiguration(configuration);if(super({name:"Tool",deps,output:{result:__name(value=>{if(typeof value!="string"||!value.trim())throw new TypeError("Tool final answer must be nonempty");return value},"result")}}),this.maxModelCalls=options.maxModelCalls===void 0?1e3:options.maxModelCalls,this.waitMs=options.waitMs===void 0?3e4:options.waitMs,this.recoverable=options.checkpoint===!0,this.budgets=resolveToolBudgets(options.budgets),!Number.isSafeInteger(this.maxModelCalls)||this.maxModelCalls<1)throw new RangeError("Tool model call limit must be a positive safe integer");if(!Number.isSafeInteger(this.waitMs)||this.waitMs<0||this.waitMs>2147483647)throw new RangeError("Tool wait must be a nonnegative timer duration");const entries=toolEntries(options.tools,this.recoverable);this.tools=Object.freeze(Object.fromEntries(entries.map(entry=>[entry.name,entry.registration]))),Object.defineProperty(this,"tools",{value:this.tools,enumerable:!0,writable:!1,configurable:!1}),this.checkToolCall=call=>selectTool(entries,call),Object.defineProperty(this,"checkToolCall",{value:this.checkToolCall,enumerable:!1,writable:!1,configurable:!1});const catalog=this.catalog=createToolCatalog(entries,options.policyVersion,{maxModelCalls:this.maxModelCalls,waitMs:this.waitMs,budgets:this.budgets}),inputCodec=inputCodecFor(catalog);for(const name of["catalog","maxModelCalls","waitMs","recoverable","budgets"])Object.defineProperty(this,name,{value:this[name],enumerable:!0,writable:!1,configurable:!1});const budgets=this.budgets,completion=createToolCompletion(this.recoverable,catalog,budgets,this.waitMs);this.stages=Object.freeze({model:completion.id,tools:Object.freeze(entries.map(entry=>Object.freeze({structureId:entry.registration.node.id,name:entry.name})))}),Object.defineProperty(this,"stages",{value:this.stages,enumerable:!0,writable:!1,configurable:!1});const tools=new Map(entries.map(entry=>[entry.name,entry])),modelReception=reception(completion,intakeCodec),toolReceptions=new Map(entries.map(entry=>[entry.name,reception(entry.registration.node,feedbackCodec)])),slots=Object.freeze(Object.fromEntries([["models",childReferences(completion)],...entries.map(entry=>[entry.name,childReferences(entry.registration.node)])])),members=Object.freeze([completion,...entries.map(entry=>entry.registration.node)]),maxModelCalls=this.maxModelCalls,recoverable=this.recoverable,point=checkpointPoint(),prepared=point({name:"prepared",state:preparedCodec,references:slots,check:__name((state,facts)=>{if(state.catalog!==catalog.digest||facts.children.values().length||Object.values(facts.references).some(refs=>refs.length))throw new Error("Prepared Tool configuration or child relations changed")},"check"),resume:{deps,uses:members,run:__name((work,state,saved)=>prepare(work,state,selections(work,saved)),"run")}}),waiting=point({name:"model-waiting",state:stateCodec,references:slots,check,resume:{deps,uses:members,run:resumeModel}}),processing=point({name:"tools",state:stateCodec,references:slots,check,resume:{deps,uses:members,run:resumeTools}}),completed=point({name:"completed",state:stateCodec,references:slots,check,resume:{deps,uses:members,run:__name(async(work,state,refs)=>(await confirmExchanges(work,state,refs),finalText(work,state)),"run")}});recoverable&&(this.checkpoint={key:"capix.tool",version:1,input:inputCodec,output:stringCodec,receptions:[modelReception,...toolReceptions.values()],points:[prepared,waiting,processing,completed]});const save=__name((work,name,state,refs)=>work.checkpoint?.({point:name==="waiting"?waiting:name==="processing"?processing:completed,state,refs}),"save"),selections=__name((work,saved)=>Object.fromEntries(Object.entries(slots).map(([name,slot])=>[name,work.checkpoint?work.checkpoint.select(slot.definition,saved?.[name]):saved?.[name]??[]])),"selections"),append=__name((refs,name,reference)=>{const selected=refsAt(refs,name);return"add"in selected?(selected.add(reference),refs):{...refs,[name]:[...selected,reference]}},"append"),toolReception=__name(name=>{const value=toolReceptions.get(name);if(!value)throw new Error("Tool reception is not registered");return value},"toolReception"),modelFact=__name((facts,ref)=>facts.require(completion,ref),"modelFact");function check(state,facts){if(state.catalog!==catalog.digest||state.id!==state.origin.id)throw new Error("Tool fixed catalog or budgets changed");const models=facts.references.models;if(!models||facts.children.values().length!==Object.values(facts.references).reduce((sum,values)=>sum+values.length,0))throw new Error("Tool checkpoint omitted child relations");if(models.length!==state.exchanges.length+1||models.length>maxModelCalls)throw new Error("Tool model prefix changed");if(models.forEach((fact,index)=>{const child2=modelFact(facts.children,fact.ref);if(!equal(child2.input,{origin:state.origin,exchanges:state.exchanges.slice(0,index)}))throw new Error("Model input differs from fixed exchange prefix");const expected=state.exchanges[index];if(expected){const receipt=facts.children.accepted(child2.ref,modelReception);if(receipt.reference.collection!==expected.collection||receipt.reference.id!==expected.id)throw new Error("Model reception differs from exchange prefix")}}),!state.current){if(state.active||state.cursor!==0)throw new Error("Waiting model has tool progress");return}const latest=models.at(-1);if(!latest)throw new Error("Current exchange lacks its model");const child=modelFact(facts.children,latest.ref),received=facts.children.accepted(child.ref,modelReception);if(state.current.collection!==received.reference.collection||state.current.id!==received.reference.id||child.returned?.status!=="succeeded")throw new Error("Current exchange differs from accepted model");const calls=child.returned.output.reply.output.filter(item=>item.kind==="tool-call");if(state.cursor>calls.length)throw new Error("Tool cursor exceeds model requests");for(let index=0;index<state.cursor;index++){const call=calls[index],selected=selectTool(entries,call);if(selected.kind==="rejected")continue;const found=facts.references[selected.entry.name]?.find(fact=>fact.order>child.order&&fact.key===call.id&&equal(fact.input,selected.input));if(!found)throw new Error("Processed tool has no exact child");const receipt=found.reception.status==="accepted"?facts.children.accepted(found.ref,toolReception(selected.entry.name)):facts.children.handled(found.ref,toolReception(selected.entry.name));if(receipt.reference.collection!==state.current.collection||receipt.reference.id!==state.current.id)throw new Error("Tool receipt belongs to another exchange")}if(state.active){const call=calls[state.cursor],relation=facts.references[state.active.name]?.[state.active.index],selected=call&&selectTool(entries,call);if(!call||selected?.kind!=="accepted"||selected.entry.name!==state.active.name||call.id!==state.active.callId||!relation||relation.order<=child.order||relation.key!==call.id||!equal(relation.input,selected.input))throw new Error("Active tool differs from fixed model request")}}__name(check,"check");async function receiveModel(work,state,refs,child){const result={succeeded:__name(({reply})=>work.resources.exchanges.intake(state.origin,assertToolReplyBytes(reply,budgets)),"succeeded")},received=recoverable?await child.invoke({reception:modelReception,result}):await child.invoke({result}),next={...state,current:received.reference,cursor:0,active:null};return save(work,"processing",next,refs),processTools(work,next,refs)}__name(receiveModel,"receiveModel");function startModel(work,state,refs){const models=refsAt(refs,"models");if(models.length>=maxModelCalls)throw new ToolModelCallLimitError(models.length,maxModelCalls);verifyOrigin(work,state);const child=work.children.create(completion,{origin:state.origin,exchanges:state.exchanges},{key:`model:${models.length}`}),nextRefs=append(refs,"models",child.ref);return save(work,"waiting",state,nextRefs),receiveModel(work,state,nextRefs,child)}__name(startModel,"startModel");async function resumeModel(work,state,saved){const refs=selections(work,saved);await confirmExchanges(work,state,refs);const child=modelFact(work.children.facts,referenceAt(refs,"models",refsAt(refs,"models").length-1));if(child.reception.status==="accepted"){const receipt=work.children.facts.accepted(child.ref,modelReception);return processTools(work,{...state,current:receipt.reference,cursor:0,active:null},refs)}return receiveModel(work,state,refs,work.children.resume(child.ref))}__name(resumeModel,"resumeModel");async function receiveTool(work,state,refs,child){if(!state.current||!state.active)throw new Error("Missing active tool state");const active=state.active,reference=state.current,entry=tools.get(active.name);if(!entry)throw new Error("Tool is not registered");const result={succeeded:__name(async value=>{entry.registration.maxOutputBytes!==void 0&&assertToolOutputBytes(value,entry.registration.maxOutputBytes);const mapped=assertToolFeedbackBytes(active.callId,await entry.registration.result.succeeded(value),budgets);return work.resources.exchanges.receive(state.origin,reference,active.callId,mapped)},"succeeded"),...entry.registration.result.failed?{failed:__name(async failure=>{if(failure instanceof ToolBudgetError)throw failure;return work.resources.exchanges.receive(state.origin,reference,active.callId,assertToolFeedbackBytes(active.callId,await entry.registration.result.failed(failure),budgets))},"failed")}:{}},receipt=recoverable?await child.invoke({reception:toolReception(active.name),result}):await child.invoke({result}),next={...state,current:receipt.reference,cursor:state.cursor+1,active:null};return save(work,"processing",next,refs),processTools(work,next,refs)}__name(receiveTool,"receiveTool");async function resumeTools(work,state,saved){const refs=selections(work,saved);if(await confirmExchanges(work,state,refs),!state.active)return processTools(work,state,refs);const ref=referenceAt(refs,state.active.name,state.active.index),fact=work.children.facts.get(ref);if(fact.reception.status==="accepted"||fact.reception.status==="handled"){const receipt=fact.reception.status==="accepted"?work.children.facts.accepted(ref,toolReception(state.active.name)):work.children.facts.handled(ref,toolReception(state.active.name));return processTools(work,{...state,current:receipt.reference,cursor:state.cursor+1,active:null},refs)}return receiveTool(work,state,refs,work.children.resume(ref))}__name(resumeTools,"resumeTools");async function confirmExchanges(work,state,refs){verifyOrigin(work,state);const modelRefs=Array.from(refsAt(refs,"models"));for(const[index,ref]of modelRefs.entries()){const fact=modelFact(work.children.facts,ref);if(fact.reception.status!=="accepted")continue;const receipt=work.children.facts.accepted(fact.ref,modelReception),entry=work.resources.exchanges.read(state.origin,receipt.reference,!1);if(assertToolReplyBytes(entry.reply,budgets),fact.returned?.status!=="succeeded"||!equal(entry.reply,fact.returned.output.reply)||!equal(entry.operation,receipt.operation))throw new Error("Exchange differs from original model return or reception");await work.resources.model.confirm({reference:fact.returned.output.reference,basis:fact.input,reply:fact.returned.output.reply,budgets});const next=modelRefs[index+1],end=next?work.children.facts.get(next).order:1/0;for(const feedback of entry.feedback){assertToolFeedbackBytes(feedback.toolCallId,{status:feedback.status,content:feedback.content},budgets);const call=entry.reply.output.find(item=>item.kind==="tool-call"&&item.id===feedback.toolCallId);if(!call||call.kind!=="tool-call")throw new Error("Feedback has no original model call");const selected=selectTool(entries,call);if(selected.kind==="rejected"){if(!equal(feedback,{toolCallId:call.id,...selected.result})||entry.receptions.some(receipt2=>receipt2.callId===call.id))throw new Error("Precheck feedback differs from fixed policy");continue}const child=Array.from(refsAt(refs,selected.entry.name)).map(ref2=>work.children.facts.get(ref2)).find(child2=>child2.order>fact.order&&child2.order<end&&child2.key===call.id&&equal(child2.input,selected.input));if(!child)throw new Error("Feedback has no exact tool child");if(child.returned?.status==="succeeded"&&selected.entry.registration.maxOutputBytes!==void 0&&assertToolOutputBytes(child.returned.output,selected.entry.registration.maxOutputBytes),child.reception.status!=="accepted"&&child.reception.status!=="handled")continue;const prior=child.reception.status==="accepted"?work.children.facts.accepted(child.ref,toolReception(selected.entry.name)):work.children.facts.handled(child.ref,toolReception(selected.entry.name));if(prior.reference.collection!==receipt.reference.collection||prior.reference.id!==receipt.reference.id||prior.feedbackDigest!==feedbackDigest(feedback)||!equal(prior.operation,entry.receptions.find(item=>item.callId===call.id)?.operation))throw new Error("Feedback differs from original tool reception")}}for(const entry of entries)for(const ref of refsAt(refs,entry.name)){const child=work.children.facts.require(entry.registration.node,ref);if(child.reception.status!=="accepted"&&child.reception.status!=="handled")continue;const receipt=child.reception.status==="accepted"?work.children.facts.accepted(ref,toolReception(entry.name)):work.children.facts.handled(ref,toolReception(entry.name)),exchange=work.resources.exchanges.read(state.origin,receipt.reference,!1),feedback=exchange.feedback.find(feedback2=>feedback2.toolCallId===child.key);if(!feedback||receipt.feedbackDigest!==feedbackDigest(feedback)||!equal(receipt.operation,exchange.receptions.find(reception2=>reception2.callId===child.key)?.operation))throw new Error("Saved tool reception has no exact exchange feedback")}}__name(confirmExchanges,"confirmExchanges");function finalText(work,state){if(!state.current)throw new Error("Missing final model exchange");const reply=work.resources.exchanges.read(state.origin,state.current,!1).reply;if(reply.output.some(item=>item.kind==="tool-call"))throw new Error("Tool final reply still contains tool calls");if(reply.termination.kind!=="completed")throw new Error("Incomplete model reply cannot finish Tool");const text=modelReplyText(reply);if(!text.trim())throw new Error("Tool final answer is empty");return text}__name(finalText,"finalText");async function processTools(work,initial,refs){let state=initial;if(!state.current)throw new Error("Missing model exchange");verifyOrigin(work,state);const entry=work.resources.exchanges.read(state.origin,state.current,!1);if(entry.reply.termination.kind!=="completed")throw new Error("Incomplete model reply cannot execute tools");const calls=entry.reply.output.filter(item=>item.kind==="tool-call");if(!calls.length){const value=finalText(work,state);return save(work,"completed",state,refs),value}for(await work.resources.model.preflight({origin:state.origin,exchanges:state.exchanges,current:state.current,budgets});state.cursor<calls.length;){const call=calls[state.cursor],selected=selectTool(entries,call);if(selected.kind==="rejected"){const current=work.resources.exchanges.complete(state.origin,state.current,call.id,assertToolFeedbackBytes(call.id,selected.result,budgets));state={...state,current,cursor:state.cursor+1},save(work,"processing",state,refs);continue}const name=selected.entry.name,child=work.children.create(selected.entry.registration.node,selected.input,{key:call.id}),currentRefs=refsAt(refs,name),active={name,index:currentRefs.length,callId:call.id},nextRefs=append(refs,name,child.ref),next={...state,active};return save(work,"processing",next,nextRefs),receiveTool(work,next,nextRefs,child)}const exchanges=[...state.exchanges,state.current];return work.resources.exchanges.closed(state.origin,exchanges),startModel(work,{...state,exchanges,current:null,cursor:0,active:null},refs)}__name(processTools,"processTools");function prepare(work,state,refs){if(state.catalog!==catalog.digest)throw new Error("Tool fixed catalog or policy changed");const origin=work.resources.exchanges.begin(state.id,{request:work.input.request,catalog});return startModel(work,{...state,origin,exchanges:[],current:null,cursor:0,active:null},refs)}__name(prepare,"prepare");function verifyOrigin(work,state){const origin=work.resources.exchanges.origin(state.origin);if(state.catalog!==catalog.digest||state.id!==origin.id||!equal(origin.request,work.input.request)||!equal(origin.catalog,catalog))throw new Error("Tool fixed origin differs from its input or execution catalog");work.resources.exchanges.closed(state.origin,state.exchanges)}__name(verifyOrigin,"verifyOrigin");const run=__name(work=>{inputCodec.encode(work.input);const state={id:randomUUID(),catalog:catalog.digest},refs=selections(work);return work.checkpoint?.({point:prepared,state,refs}),prepare(work,state,refs)},"run");Object.defineProperty(this,"run",{get:__name(()=>run,"get"),enumerable:!0,configurable:!0}),this.add(...members)}}function snapshotConfiguration(value){if(!value||typeof value!="object"||Array.isArray(value)||types.isProxy(value))throw new TypeError("Tool configuration must be a data object");const descriptors=Object.getOwnPropertyDescriptors(value);if(Object.getOwnPropertySymbols(value).length||Object.keys(descriptors).some(key=>!["tools","policyVersion","checkpoint","maxModelCalls","waitMs","budgets"].includes(key)))throw new TypeError("Unknown Tool configuration field");const result={};for(const key of["tools","policyVersion","checkpoint","maxModelCalls","waitMs","budgets"]){const field=Object.getOwnPropertyDescriptor(value,key);if(!field&&key in value||field&&!("value"in field))throw new TypeError("Tool configuration must use own data properties");if(field&&!field.enumerable)throw new TypeError("Tool configuration fields must be enumerable");field&&(result[key]=field.value)}if(result.checkpoint!==void 0&&typeof result.checkpoint!="boolean")throw new TypeError("Tool checkpoint must be boolean");if(result.budgets!==void 0){const budgets=result.budgets;if(!budgets||typeof budgets!="object"||Array.isArray(budgets)||types.isProxy(budgets)||Object.getOwnPropertySymbols(budgets).length)throw new TypeError("Tool budgets must be a data object");const snapshot={};for(const[key,descriptor]of Object.entries(Object.getOwnPropertyDescriptors(budgets))){if(!["maxReplyBytes","maxFeedbackBytes","maxRequestBytes","maxWireRequestBytes","maxCollectionBytes"].includes(key)||!descriptor.enumerable||!("value"in descriptor))throw new TypeError("Tool budgets must contain known data properties");snapshot[key]=descriptor.value}result.budgets=Object.freeze(snapshot)}return Object.freeze(result)}__name(snapshotConfiguration,"snapshotConfiguration");export{Tool,ToolModelCallLimitError};
@@ -0,0 +1,30 @@
1
+ import type { CallableInput, CallableOutput, Stable } from '@weiliang0121/capix-contracts';
2
+ import type { ChildCallable } from '@weiliang0121/capix-structure';
3
+ import type { ModelRequest, ToolResult } from '@weiliang0121/capix-model-provider';
4
+ import type { z } from 'zod';
5
+ import type { ToolByteBudgets } from './budget.js';
6
+ export interface ToolMember<Member extends ChildCallable> {
7
+ readonly node: Member;
8
+ readonly description: string;
9
+ readonly input: z.ZodType<CallableInput<NoInfer<Member>>>;
10
+ readonly maxOutputBytes?: number;
11
+ readonly result: {
12
+ readonly succeeded: (value: Stable<CallableOutput<NoInfer<Member>>>) => ToolResult | Promise<ToolResult>;
13
+ readonly failed?: (failure: unknown) => ToolResult | Promise<ToolResult>;
14
+ };
15
+ }
16
+ export type ToolMembers<Definitions extends Readonly<Record<string, ChildCallable>>> = {
17
+ readonly [Name in keyof Definitions]: ToolMember<Definitions[Name]>;
18
+ };
19
+ export type ToolBaseRequest = Omit<ModelRequest, 'tools' | 'exchanges'>;
20
+ export interface ToolInput {
21
+ readonly request: ToolBaseRequest;
22
+ }
23
+ export interface ToolConfiguration<Definitions extends Readonly<Record<string, ChildCallable>>> {
24
+ readonly tools: ToolMembers<Definitions>;
25
+ readonly policyVersion: string;
26
+ readonly checkpoint?: boolean;
27
+ readonly maxModelCalls?: number;
28
+ readonly waitMs?: number;
29
+ readonly budgets?: Partial<ToolByteBudgets>;
30
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ /*! MIT License | Copyright (c) 2026 wei.liang | See LICENSE */
package/package.json CHANGED
@@ -1,6 +1,54 @@
1
1
  {
2
2
  "name": "@weiliang0121/capix-tool",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.3.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js"
10
+ }
11
+ },
12
+ "dependencies": {
13
+ "zod": "4.6.5",
14
+ "@weiliang0121/capix-contracts": "0.3.0",
15
+ "@weiliang0121/capix-diagnostics": "0.3.0",
16
+ "@weiliang0121/capix-checkpoint": "0.3.0",
17
+ "@weiliang0121/capix-interaction": "0.3.0",
18
+ "@weiliang0121/capix-resources": "0.3.0",
19
+ "@weiliang0121/capix-structure": "0.3.0",
20
+ "@weiliang0121/capix-model-provider": "0.3.0",
21
+ "@weiliang0121/capix-model-exchanges": "0.3.0"
22
+ },
23
+ "description": "A native model tool-calling Group preset for CAPiX.",
24
+ "license": "MIT",
25
+ "author": "wei.liang (https://github.com/weiliang0121)",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/weiliang0121/capix.git",
29
+ "directory": "packages/tool"
30
+ },
31
+ "engines": {
32
+ "node": ">=22.23.2"
33
+ },
34
+ "main": "./dist/index.js",
35
+ "types": "./dist/index.d.ts",
36
+ "files": [
37
+ "dist/**/*.js",
38
+ "dist/**/*.d.ts",
39
+ "LICENSE",
40
+ "README.md"
41
+ ],
42
+ "publishConfig": {
43
+ "access": "public",
44
+ "registry": "https://registry.npmjs.org"
45
+ },
46
+ "devDependencies": {
47
+ "@weiliang0121/capix-runtime": "0.3.0"
48
+ },
49
+ "scripts": {
50
+ "build": "tsc -p tsconfig.json",
51
+ "typecheck": "tsc -p tsconfig.json --noEmit",
52
+ "test": "node ../../scripts/test/run.mjs --package ."
53
+ }
6
54
  }