docs(i18n): refresh core translations for latest master
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
code-runtime.md: a984c0f6422defc879086ff95eb94048aaa6e285
|
||||
code-runtime.zh.md: 95287f2917c8fd409944569a2f5c3e8417a18efe
|
||||
code-runtime.md: 64de3c45d4f1d1d981daa6c6f074abb667e0aa52
|
||||
code-runtime.zh.md: 4b14aeb2183010e8140540258ce8109df9f59910
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](code-runtime.md) | 中文
|
||||
|
||||
代码执行 seam 是一个[能力 seam](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md):其接口([dsh-code-runtime](../../packages/code-runtime/code-runtime),`ctx.codeRuntime`)针对宿主提供的异步 binding 运行一段模型编写的程序,并报告其打印内容与返回值。代码执行是**一项可选能力**,不属于 agent loop(智能体循环)主干,因此其词汇定义在此而非 [core.md](core.md) 中。各后端的执行基底与源语言不同,这两项均为服务上的只读描述符;worker-thread 后端和工具注册表消费方(Code Mode)由 [Code Mode Agent Note(agent 决策记录)](../../.agents/notes/implemented/feature/2026-06-15-code-mode.md) 规定。
|
||||
代码执行 seam 是一个[能力 seam](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md):其接口([dsh-code-runtime](../../packages/code-runtime/code-runtime),`ctx.codeRuntime`)针对宿主提供的异步 binding 运行一段模型编写的程序,并报告其打印内容与返回值。代码执行是**一项可选能力**,不属于 agent loop(智能体循环)主干,因此其词汇定义在此而非 [core.md](core.md) 中。各后端的执行基底与源语言不同,这两项均为服务上的只读描述符;worker-thread 后端与工具注册表消费方的契约见 [Code Mode 基础设计](../../.agents/notes/implemented/feature/2026-06-15-code-mode.md)和[类型化返回契约](../../.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.md)。
|
||||
|
||||
源码:[`packages/code-runtime/code-runtime/src/types.ts`](../../packages/code-runtime/code-runtime/src/types.ts)
|
||||
|
||||
@@ -47,12 +47,12 @@ interface CodeRunRequest {
|
||||
interface CodeRunResult {
|
||||
/**
|
||||
* The program's completion value (its top-level `return`), when it ran to
|
||||
* completion and the value survived the runtime's serialization boundary;
|
||||
* a non-transferable value is replaced by a string rendering, and a failed
|
||||
* or value-less run leaves this absent.
|
||||
* completion and the value crossed the runtime's lossless-JSON boundary.
|
||||
* Invalid or over-limit completions fail the run instead of substituting a
|
||||
* rendered string; a failed or value-less run leaves this absent.
|
||||
*/
|
||||
value?: unknown
|
||||
/** Text the program emitted, in order (capped by the implementation). */
|
||||
value?: CodeJsonValue
|
||||
/** Text the program emitted, in order, bounded only as part of the outer result. */
|
||||
logs: string[]
|
||||
/** Present iff the run failed; see {@link CodeRunFailure} for the taxonomy. */
|
||||
error?: CodeRunFailure
|
||||
@@ -61,7 +61,23 @@ interface CodeRunResult {
|
||||
|
||||
## 绑定:宿主函数作为程序全局变量
|
||||
|
||||
每个 `CodeBindingNamespace` 在程序内成为一个由异步可调用函数组成的全局对象(Code Mode 消费方传入一个:`tools`)。参数与返回值必须可 structured-clone(运行时可能跨序列化边界桥接调用),且运行时将绑定名视为不可信输入(`__proto__` 是普通自有属性,绝不会发生原型碰撞):
|
||||
每个 `CodeBindingNamespace` 在程序内成为一个由异步可调用函数组成的全局对象(Code Mode 消费方传入一个:`tools`)。参数与返回值必须是无损 JSON,且跨越边界时不受 seam 层字节上限约束;运行时可以通过结构化克隆桥接它们。命名空间可以声明程序可见的错误类,而无需让运行时知道消费方的名称:运行时会注入真实构造函数,并将被拒绝的调用转为该类的实例。运行时也将绑定名视为不可信输入(`__proto__` 是普通自有属性,绝不会发生原型碰撞):
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Program-visible typed rejection for one binding namespace. The runtime
|
||||
* injects a real error constructor under `name`; rejected member calls become
|
||||
* its instances and expose the exact member name through
|
||||
* `memberNameProperty`. Both strings are runtime data rather than knowledge
|
||||
* of a particular consumer such as Code Mode.
|
||||
*/
|
||||
interface CodeBindingErrorClass {
|
||||
/** Constructor global and resulting `Error.name` (must be a usable JS identifier). */
|
||||
name: string
|
||||
/** Non-empty own property for the member name; cannot replace `name`, `message`, or `stack`. */
|
||||
memberNameProperty: string
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
@@ -76,24 +92,32 @@ interface CodeBindingNamespace {
|
||||
global: string
|
||||
/** The callable members, keyed by the exact name the program calls. */
|
||||
functions: Record<string, CodeBindingFunction>
|
||||
/** Optional program-visible typed rejection contract for this namespace. */
|
||||
errorClass?: CodeBindingErrorClass
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** A lossless JSON value transferable across the dependency-light code-runtime seam. */
|
||||
type CodeJsonValue = null | boolean | number | string | CodeJsonValue[] | { [key: string]: CodeJsonValue }
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* One host-side function exposed to the program as an async callable. The
|
||||
* runtime bridges calls to it (possibly across a serialization boundary), so
|
||||
* `args` and the resolution value MUST be structured-cloneable; a runtime
|
||||
* rejects a non-cloneable value with a descriptive error rather than
|
||||
* corrupting the run. A rejection of this function surfaces inside the
|
||||
* program as a rejection of the corresponding call.
|
||||
* `args` and the resolution value MUST be lossless JSON. A runtime rejects a
|
||||
* lossy or non-cloneable value with a descriptive error rather than corrupting
|
||||
* the run. No seam-level byte cap applies to a binding resolution. A rejection
|
||||
* of this function surfaces inside the program as a rejection of the
|
||||
* corresponding call.
|
||||
*/
|
||||
type CodeBindingFunction = (args: unknown) => Promise<unknown>
|
||||
type CodeBindingFunction = (args: unknown) => Promise<CodeJsonValue>
|
||||
```
|
||||
|
||||
## 捕获的输出与失败分类体系
|
||||
|
||||
日志是按发出顺序排列的纯字符串。运行时捕获程序的 console 与流输出,但通道和 console 方法的元数据不属于 seam 的一部分,因为消费方只渲染文本。实现对聚合输出设上限,并在输出内标记截断。
|
||||
日志是按发出顺序排列的纯字符串。运行时捕获程序的 console 与流输出,但通道和 console 方法的元数据不属于 seam,因为消费方只渲染文本。实现会对序列化后的外层日志数组,以及完成值或失败消息的组合载荷设置上限;固定的结果封装语法与消费方展示空白不计入这份可变载荷计量。超限会显式失败,而不会在值中插入替代内容。
|
||||
|
||||
失败类型是**正交的结果,独立报告**(见 [defensive-patterns](../defensive-patterns.md)):预算耗尽不是异常,中止不是超时,基底崩溃(如 OOM)也不是二者中的任何一个:
|
||||
|
||||
@@ -107,10 +131,12 @@ type CodeBindingFunction = (args: unknown) => Promise<unknown>
|
||||
* - `'timeout'` — an implementation-owned budget expired; the message says which.
|
||||
* - `'abort'` — {@link CodeRunRequest.signal} fired.
|
||||
* - `'worker-exit'` — the execution substrate died without settling (e.g. OOM).
|
||||
* - `'invalid-output'` — the completion value was not lossless JSON.
|
||||
* - `'output-limit'` — the serialized outer logs/value/diagnostic exceeded the configured cap.
|
||||
*/
|
||||
interface CodeRunFailure {
|
||||
/** The failure class (see the interface doc for each kind's meaning). */
|
||||
kind: 'exception' | 'timeout' | 'abort' | 'worker-exit'
|
||||
kind: 'exception' | 'timeout' | 'abort' | 'worker-exit' | 'invalid-output' | 'output-limit'
|
||||
/** Human-readable detail, suitable for feeding back to a model to self-correct. */
|
||||
message: string
|
||||
}
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
core.md: 7d0f9503dfd4c47f185d2bcef84528145d66fa70
|
||||
core.zh.md: 21f417bdf8ddb6c4d629de037d95a0196961aeb3
|
||||
core.md: 9446152b909cd3e0105fe44321b16353228c0730
|
||||
core.zh.md: 55b04e3ef4215fc984824015761f5f41537656a0
|
||||
@@ -13,7 +13,7 @@ harness 是一个微内核:一个极小的核心加上众多插件。大多数
|
||||
1. 它流经 agent loop 主干——循环在每个轮次中持有、派生、流式输出或记录它(`Message`、`StreamChunk`、`SessionEvent`、`Agent` 句柄本身),与当前加载了哪些插件无关;**或者**
|
||||
2. 它是插件作者面向某条流水线编写的唯一标题类型——`ToolDefinition`(每个工具*是什么*)。
|
||||
|
||||
其他一切都记录在**子页面**上,而非本页。划线的规则是:*你编写、持有或接收的类型是核心;为它提供类型推导、渲染或持久化的机制是子页面细节*。因此 `ToolDefinition` 是核心,但为它提供类型推导的 `SchemaSpec`/`InferArgs` DSL、为它提供渲染意图的 `ToolCallView`/`ToolResultView` 词汇,以及存储事件日志的 `SessionPersistence` seam 都不是——它们分别在下列子页面中。
|
||||
其他一切都记录在**子页面**上,而非本页。划线的规则是:*你编写、持有或接收的类型是核心;为它提供类型推导、渲染或持久化的机制是子页面细节*。因此 `ToolDefinition` 是核心,但为它提供类型推导的 `ValueSchemaSpec`/`ParameterSchemaSpec` 机制、为它提供渲染意图的 `ToolCallView`/`ToolResultView` 词汇,以及存储事件日志的 `SessionPersistence` seam 都不是——它们分别在下列子页面中。
|
||||
|
||||
| 子页面 | 负责内容 |
|
||||
|---|---|
|
||||
@@ -552,4 +552,4 @@ type SessionStartSource = 'startup' | 'resume' | 'clear' | 'compact'
|
||||
|
||||
唯一属于核心的流水线编写类型:每个已注册工具*是什么*——一个面向模型的 `ToolSchema` 加上一个 `execute` 函数和可选的 UI 展示器。工具作者很少手动构造它(`defineTool` DSL 会用类型化参数构建),但它是注册表持有、循环分发所经过的契约。
|
||||
|
||||
其完整字段、`defineTool`/`SchemaSpec`/`InferArgs` 类型化 schema DSL、`ToolExecution`/`ToolExecutionResult` waterfall 形状,以及工具展示 UI 词汇在 **[tools.md](tools.md)** 中。
|
||||
其完整字段、`defineTool`/`ValueSchemaSpec`/`ParameterSchemaSpec` 类型化 schema DSL、`ToolExecution`/`ToolExecutionResult` waterfall 形状,以及工具展示 UI 词汇在 **[tools.md](tools.md)** 中。
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
filesystem.md: 3c9b041da92e71cd20429e8b1549c0b8e6f2436d
|
||||
filesystem.zh.md: 612b90427c6813823d38515257acfdfafcc2ba1e
|
||||
filesystem.md: 110c1fd428b15c5094f9dcc94050cad61c324373
|
||||
filesystem.zh.md: aca450364c05c6f756c36fccc11be7246767f3a4
|
||||
@@ -207,7 +207,7 @@ interface FsPolicyExec {
|
||||
|
||||
## 读取结果(消费方 / 读取渲染)
|
||||
|
||||
文本读取受行窗口、字节上限和后端限制约束。面向模型的 `read` 工具渲染的结果纯粹是展示性的;不存在 `full`/`partial` 视图区分——授权基于新鲜度(工具直接用 stat 的版本 emit `fs/observed`),因此任何窗口化读取在文件未变时都能授权后续的 write/edit。读取窗口化与此结果形状位于 `dsh-tool-fs`(拥有读取操作的执行器)中,而非策略插件中。
|
||||
文本读取受行窗口、字节上限和后端限制约束。达到字节上限后,扫描仍会继续,但不再保留更多行,因此 `totalLines` 仍为精确值。面向模型的 `read` 工具渲染的结果纯粹是展示性的;不存在 `full`/`partial` 视图区分——授权基于新鲜度(工具直接用 stat 的版本 emit `fs/observed`),因此任何窗口化读取在文件未变时都能授权后续的 write/edit。读取窗口化与此结果形状位于 `dsh-tool-fs`(拥有读取操作的执行器)中,而非策略插件中。
|
||||
|
||||
```ts type-equiv
|
||||
/** Outcome of a bounded text read — what {@link formatReadOutput} renders. */
|
||||
@@ -216,9 +216,9 @@ interface FileReadOutcome {
|
||||
offset: number
|
||||
/** Returned lines, already numbered. */
|
||||
lines: FileTextLine[]
|
||||
/** Total line count in the file, unless `truncatedByBytes` stopped scanning early. */
|
||||
/** Exact total line count in the file. */
|
||||
totalLines: number
|
||||
/** Whether selected output hit the byte cap before EOF or the requested limit. */
|
||||
/** Whether selected output hit the byte cap. */
|
||||
truncatedByBytes?: true
|
||||
}
|
||||
```
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
session.md: e07e38037e51c74886db88d24353266f6035b15a
|
||||
session.zh.md: 8d3de643a42bdd173528b024276d0f83e90d2d11
|
||||
session.md: b342e1c5c3bff030d67c61a6f1daa0c8167182c1
|
||||
session.zh.md: 8e8a3f923b2f4ec1dd5d86ebe6bb7eff0511474c
|
||||
@@ -87,15 +87,25 @@ interface SessionEventMap {
|
||||
*/
|
||||
'tool/call': { turn: number; step: number; callId: CallId; name: string; arguments: string }
|
||||
/**
|
||||
* A completed tool call's model-facing result, plus an optional tool-private
|
||||
* `meta` presentation payload. `meta` is opaque to the core (`unknown` — the
|
||||
* producing tool owns its shape and reads it back in `presentResult`) but MUST
|
||||
* be JSON-serializable: `Session.append` runtime-validates all event data with
|
||||
* `isJsonValue`, so a non-serializable `meta` is rejected at the source, and the
|
||||
* durable log reproduces the identical card on replay. Absent unless the tool
|
||||
* attaches one (e.g. `dsh-tool-fs` carries its result-time contextual diff here).
|
||||
* A completed tool call's model-facing result, optional internal failure
|
||||
* identity, and optional tool-private `meta` presentation payload. `meta` is
|
||||
* opaque to the core (the producing tool owns its shape and reads it back in
|
||||
* `presentResult`) but MUST be JSON-serializable: `Session.append`
|
||||
* runtime-validates all event data with `isJsonValue`, so a non-serializable
|
||||
* `meta` is rejected at the source, and the durable log reproduces the
|
||||
* identical card on replay. Absent
|
||||
* unless the tool attaches one (e.g. `dsh-tool-fs` carries its result-time
|
||||
* contextual diff here).
|
||||
*/
|
||||
'tool/result': { turn: number; step: number; callId: CallId; content: ContentBlock[]; isError: boolean; error?: { name: string; code: string }; meta?: unknown }
|
||||
'tool/result': {
|
||||
turn: number
|
||||
step: number
|
||||
callId: CallId
|
||||
content: ContentBlock[]
|
||||
isError: boolean
|
||||
error?: { name: string; code: string }
|
||||
meta?: JsonValue
|
||||
}
|
||||
/** Steering content injected between steps of a running turn. */
|
||||
'steering/message': PromptMessageData & { turn: number }
|
||||
/** Whole-list snapshot; latest write wins on replay. Log-only UI state; never derived history. */
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
subagent.md: 97d6862c10a0757c41472f207f857c25f3f5d50f
|
||||
subagent.zh.md: d28e18df6361c4a87a66b027f20cf584b8b14ecb
|
||||
subagent.md: 0335a3f0780ae17b57ae730f5a49a269261c8073
|
||||
subagent.zh.md: 0f255ac79258d91a3305c4e2a9c9f943f5674c1c
|
||||
@@ -68,11 +68,11 @@ interface SubagentStartRequest {
|
||||
/** Per-child agent options (model and plugin-defined extension fields). */
|
||||
readonly agentOptions?: AgentOptions
|
||||
/**
|
||||
* Object-rooted JSON Schema within `assertSupportedOutputSchema`'s enforced subset. Start rejects
|
||||
* Object-rooted JSON Schema within `assertObjectJsonSchema`'s enforced subset. Start rejects
|
||||
* unsupported schemas or providers without the capability. Data must be plain host-realm JSON;
|
||||
* a successful child returns the matching value as {@link SubagentResult.structured}.
|
||||
*/
|
||||
readonly outputSchema?: StructuredOutputSchema
|
||||
readonly outputSchema?: ObjectJsonSchema
|
||||
/**
|
||||
* Optional absolute delegation-depth cap for the child being started: its
|
||||
* computed depth must be less than or equal to this non-negative safe
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
tools.md: ce14a37da33f89b8b90d6d8e70756f94e3d690dd
|
||||
tools.zh.md: 77b1c9534c5eae8843e458eedb6af0e8950375ae
|
||||
tools.md: 4612800ce0b2b2d718cb32c1aad9fb2e592c337b
|
||||
tools.zh.md: 5663ecef53654c1fc0f8c1d8661ff34cbcd5eb88
|
||||
@@ -8,21 +8,36 @@
|
||||
|
||||
## `ToolDefinition` — 一个已注册的工具
|
||||
|
||||
由一个 `ToolSchema`(面向模型的字段)、`execute` 函数、仅供宿主使用的调度器元数据和可选 UI 展示函数组成。注册表持有这些定义,循环通过它们分派调用。注册表的 `schemas()` 通过显式允许列表构建面向模型的 `ToolSchema[]`;`execute`/`timeoutMs`/`isConcurrencySafe`/`presentCall`/`presentResult` 绝不能泄漏到模型请求中。
|
||||
由一个 `ToolSchema`(面向模型的字段)、必需的规范输出声明、`execute` 函数、仅供宿主使用的调度器元数据和可选 UI 展示函数组成。注册表持有这些定义,循环通过它们分派调用。注册表的 `schemas()` 通过显式允许列表构建面向模型的 `ToolSchema[]`;`output`/`execute`/`timeoutMs`/`isConcurrencySafe`/`presentCall`/`presentResult` 绝不能泄漏到模型请求中。
|
||||
|
||||
```ts type-equiv
|
||||
/** Tool-owned canonical output contract used after the body returns a JSON value. */
|
||||
interface ToolOutputDefinition {
|
||||
/** Raw supported JSON Schema enforced against every successful canonical value. */
|
||||
readonly schema: JsonSchemaNode
|
||||
/** Pure projection from validated arguments and value to Native/model content. */
|
||||
render(args: unknown, value: JsonValue): ContentBlock[]
|
||||
/** Pure replayable presentation projection, computed only for surface calls. */
|
||||
presentationMeta?(args: unknown, value: JsonValue): JsonValue
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** A registered tool: its schema plus the execution function. */
|
||||
interface ToolDefinition extends ToolSchema {
|
||||
/** Mandatory canonical output declaration. */
|
||||
readonly output: ToolOutputDefinition
|
||||
/**
|
||||
* Run one accepted call. Async work must observe or forward `exec.signal` and
|
||||
* settle only after its owned work reaches quiescence. The registry preserves
|
||||
* caller cancellation through around-dispatch signal replacement and does
|
||||
* not abandon this promise, but it cannot hard-kill same-process code.
|
||||
* Run one accepted call and return only its canonical lossless-JSON value.
|
||||
* Async work must observe or forward `exec.signal` and settle only after its
|
||||
* owned work reaches quiescence. The registry preserves caller cancellation
|
||||
* through around-dispatch signal replacement and does not abandon this
|
||||
* promise, but it cannot hard-kill same-process code.
|
||||
* @param args - losslessly snapshotted, frozen model arguments.
|
||||
* @param exec - execution identity, cancellation signal, and context deferral.
|
||||
* @returns model-facing content plus optional private presentation metadata.
|
||||
* @returns the canonical value declared by `output.schema`.
|
||||
*/
|
||||
execute(args: unknown, exec: ToolRunContext): Promise<ToolExecuteReturn>
|
||||
execute(args: unknown, exec: ToolRunContext): Promise<unknown>
|
||||
/**
|
||||
* Cooperative tool-call timeout budget in milliseconds. Omit for no deadline.
|
||||
* Enforced by `@deepseek-ai/dsh-timeout-policy` (a `tools/execute` wrapper); it
|
||||
@@ -57,7 +72,7 @@ interface ToolDefinition extends ToolSchema {
|
||||
presentCall?(args: unknown): ToolCallView | undefined
|
||||
/**
|
||||
* Optional: how to present the COMPLETED state, given the same `args` and the
|
||||
* `result` (`execute`'s content + whether it errored). Returns a
|
||||
* durable result projection (`content`, failure state, and optional `meta`). Returns a
|
||||
* {@link ToolResultView}, or `undefined` (or omit the method) to keep the
|
||||
* pending title and render the raw result content. Pure and side-effect-free
|
||||
* for the same replay reason.
|
||||
@@ -66,69 +81,62 @@ interface ToolDefinition extends ToolSchema {
|
||||
}
|
||||
```
|
||||
|
||||
`execute` 接收 `args: unknown`——原始的 `ToolDefinition` 自行校验输入。第一方工具不需要手写校验;它们使用 `defineTool`,由后者代为校验并收窄类型。
|
||||
`execute` 接收 `args: unknown`——原始的 `ToolDefinition` 自行校验输入。第一方工具不需要手写校验;它们使用 `defineTool`,由后者代为校验并收窄参数类型、根据 `output.schema` 推导函数体返回类型,并为两个输出投影器提供类型约束。
|
||||
|
||||
## 类型化 schema DSL
|
||||
## 统一的 JSON 值 schema DSL
|
||||
|
||||
插件作者为每个属性编写带有布尔值 `required: true` 的规格,类型层面的辅助工具将规格映射为 `execute` 的参数类型——零类型断言。该 DSL 是*提供类型推导的机制*,作用于 `ToolDefinition`;它有意作为子页面细节,而非核心内容。
|
||||
插件作者使用同一套词汇描述类型化参数和类型化输出值。`ValueSchemaSpec` 支持 `string`、`number`、`integer`、`boolean`、`null`、`array`、`object`、仅作者侧可用的 `json`,以及要求恰好命中一个分支的 `oneOf`;标量 `enum` 和 `const` 值必须与节点类型匹配。显式对象节点始终声明 `additionalProperties: true | false`。参数定义仍是隐式的开放对象属性映射,每个必填属性都附带 `required: true`。
|
||||
|
||||
源码:[`packages/core/tools/src/schema.ts`](../../packages/core/tools/src/schema.ts)
|
||||
|
||||
```ts type-equiv
|
||||
/** One schema-spec property entry. */
|
||||
interface SchemaProp {
|
||||
type: SchemaType
|
||||
/** Per-property required flag (NOT the JSON Schema top-level required array). */
|
||||
required?: true
|
||||
/** Human-readable description, surfaced in the JSON Schema as well. */
|
||||
description?: string
|
||||
/** Enum of allowed values (strings only). */
|
||||
enum?: string[]
|
||||
/**
|
||||
* Model-visible JSON Schema default annotation. Validation does not apply it;
|
||||
* dynamic tool mounts may supply it even though first-party definitions do not.
|
||||
*/
|
||||
default?: unknown
|
||||
/** Nested properties for type: 'object'. */
|
||||
properties?: SchemaSpec
|
||||
/** Items schema for type: 'array'. */
|
||||
items?: SchemaProp
|
||||
/** One author-facing schema for any lossless JSON value root. */
|
||||
type ValueSchemaSpec =
|
||||
| StringValueSchemaSpec
|
||||
| NumberValueSchemaSpec
|
||||
| IntegerValueSchemaSpec
|
||||
| BooleanValueSchemaSpec
|
||||
| NullValueSchemaSpec
|
||||
| ArrayValueSchemaSpec
|
||||
| ObjectValueSchemaSpec
|
||||
| JsonValueSchemaSpec
|
||||
| OneOfValueSchemaSpec
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** One implicit parameter-root property, optionally required. */
|
||||
type ParameterPropertySpec = ValueSchemaSpec & { required?: true }
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Tool parameter schema. The map itself is an implicit open object root;
|
||||
* requiredness remains a per-property `required: true` annotation.
|
||||
*/
|
||||
type ParameterSchemaSpec = {
|
||||
[key: string]: ParameterPropertySpec
|
||||
[key: symbol]: never
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* The author-facing parameter schema: a shallow map of property name to
|
||||
* {@link SchemaProp}. Required-ness is a per-property boolean (`required:
|
||||
* true`), not a separate array.
|
||||
*/
|
||||
type SchemaSpec = Record<string, SchemaProp>
|
||||
```
|
||||
|
||||
`SchemaType` 是原始联合类型 `'string' | 'number' | 'boolean' | 'object' | 'array'`。`InferArgs<S>` 将一个 `SchemaSpec` 映射为 TS 参数类型——`required: true` 的属性成为必选键,其余为真正的可选:
|
||||
`{ type: 'json' }` 推导为 `JsonValue`,并编译成仅含注解、不施加约束的原始 schema。输出根可以是对象、数组、标量或 null。`InferValue<S>` 在 16 层容器内保留字面量约束与对象开放性,之后回退为 `JsonValue`,避免耗尽 TypeScript 的类型实例化栈。`InferArgs<P>` 依据逐属性的必填标记生成必填和可选的字符串键:
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Infer the TS argument type for a complete {@link SchemaSpec}.
|
||||
*
|
||||
* Properties marked `required: true` are required keys; all others are
|
||||
* genuinely optional keys (`?`), so callers may omit them entirely.
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* type Args = InferArgs<{ path: { type: 'string'; required: true }; limit: { type: 'number' } }>
|
||||
* // → { path: string; limit?: number }
|
||||
* ```
|
||||
* Infer the TypeScript value accepted by an author-facing value schema. Exact
|
||||
* inference is bounded to 16 container levels, then falls back to `JsonValue`.
|
||||
*/
|
||||
type InferArgs<S extends SchemaSpec> = Simplify<
|
||||
& { [K in RequiredKeys<S>]: InferPropValue<S[K]> }
|
||||
& { [K in Exclude<keyof S, RequiredKeys<S>>]?: InferPropValue<S[K]> }
|
||||
>
|
||||
type InferValue<S> = InferValueAt<S, []>
|
||||
```
|
||||
|
||||
`defineTool({ name, description, parameters, execute, … })` 将各部分串联:`parameters` 是一个 `SchemaSpec`,`execute(args, exec)` 获得 `args: InferArgs<typeof parameters>`,辅助函数将规格转换为 JSON Schema(`schemaSpecToJsonSchema`)用于协议传输,并在类型化函数体运行前校验模型生成的参数(`validateArgs`)。校验不通过时抛出 `ToolArgsError`(`code: 'INVALID_ARGS'`),注册表将其转为 `isError` 结果以便模型自行修正。为何用自定义 DSL 而非 schemastery:工具参数需要 JSON Schema(LLM(大语言模型)的协议格式),而非校验/转换——轻量 DSL 以最小的接口面积提供最佳的编写体验。
|
||||
```ts type-equiv
|
||||
/** Infer the TypeScript argument object for an implicit parameter schema. */
|
||||
type InferArgs<S> = InferProperties<S, []>
|
||||
```
|
||||
|
||||
注册是一个受信任的同进程契约。注册表以 readonly 输入借用类型化定义,仅校验语义要求(如 `timeoutMs` 必须为正有限值);`schemas()` 在模型边界处物化显式的面向模型投影,使执行和展示共享同一份已解析定义,而不会将回调泄漏到协议上。
|
||||
`defineTool({ name, description, parameters, output, execute, … })` 将参数推导与 `parameterSchemaSpecToJsonSchema()` 和 `validateArgs()` 绑定,并将 `execute`/`render`/`presentationMeta` 与 `InferValue<OutputSchema>` 绑定。Schema 记录只包含自有且可枚举的字符串键,schema 数组是稠密的内建数组,因此推导、编译与校验观察到的是同一份声明。精确推导保持到 16 层容器,之后放宽为 `JsonValue`;运行时校验仍会继续遍历完整 schema。`valueSchemaSpecToJsonSchema()` 通过同一套已强制执行的原始子集编译输出声明。参数不匹配时抛出 `ToolArgsError`(`INVALID_ARGS`);函数体或后置策略产生的值无效时抛出 `ToolOutputError`(`INVALID_TOOL_OUTPUT`)。两者都经由常规工具错误路径处理。原始 JSON Schema 默认保持开放;不支持的关键字会被拒绝,而不会在未强制执行的情况下获准进入。
|
||||
|
||||
注册是一个受信任的同进程契约。注册表以 readonly 输入借用类型化定义,要求它声明 `output`,校验其原始 schema,并检查 `timeoutMs` 必须为正有限值等语义要求;`schemas()` 在模型边界处物化显式的面向模型投影,使执行和展示共享同一份已解析定义,而不会将回调泄漏到协议上。
|
||||
|
||||
## `ToolRestriction` — 单个作用域的实时全局过滤器
|
||||
|
||||
@@ -254,34 +262,48 @@ type ToolGuard = (execution: Readonly<ToolExecution>) => string | undefined
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** The outcome of one tool call. */
|
||||
interface ToolExecutionResult {
|
||||
content: ContentBlock[]
|
||||
isError: boolean
|
||||
/**
|
||||
* Set when the call failed with a {@link HarnessError}: machine-routable
|
||||
* `{ name, code }` for retry/sandbox plugins and replay. The model-facing
|
||||
* text in `content` is always present; this is extra structure for code.
|
||||
*/
|
||||
error?: ToolErrorInfo
|
||||
/**
|
||||
* Model-facing context for the next request, separate from this tool result. The loop
|
||||
* accepts it into the active-batch FIFO, then appends after recorded results even if interrupted.
|
||||
*/
|
||||
additionalContexts?: HookContext[]
|
||||
/**
|
||||
* The tool-private presentation payload from a successful `execute` (the object
|
||||
* return form). Threaded onto the `tool/result` session event and back into
|
||||
* {@link ToolResult} for `presentResult`. Opaque (`unknown`); absent when the
|
||||
* tool attached none or the call failed.
|
||||
*/
|
||||
meta?: unknown
|
||||
/** Canonical failure detail; internal routing information remains optional. */
|
||||
interface ToolFailure {
|
||||
/** Human-readable failure message without the Native `Error: ` envelope. */
|
||||
message: string
|
||||
/** Internal error class/code used by policy and durable diagnostics. */
|
||||
info?: ToolErrorInfo
|
||||
}
|
||||
```
|
||||
|
||||
结果仅承载产出。调用身份保留在不可变的 `ToolExecution` 上,后者伴随结果经过每个钩子,并出现在持久化的 `tool/call` / `tool/result` 会话事件上,因此包装层无法创建第二个相互矛盾的身份。
|
||||
```ts type-equiv
|
||||
/** Successful canonical tool execution, including its Native/model projection. */
|
||||
interface ToolExecutionSuccess {
|
||||
readonly isError: false
|
||||
/** Execution-local canonical value; deliberately omitted from durable events. */
|
||||
readonly value: JsonValue
|
||||
readonly content: ContentBlock[]
|
||||
readonly error?: never
|
||||
readonly meta?: JsonValue
|
||||
readonly additionalContexts?: HookContext[]
|
||||
}
|
||||
```
|
||||
|
||||
注册表在 `tools/result` 之前立即物化并冻结最终接受的结果。其内容、结构化错误、附加上下文和展示元数据必须通过 JSON 无损往返;无效的产出会被转为 JSON 安全的 `isError` 结果,从而保证被观察到的实时产出对后续持久化的 `tool/result` 追加是安全的。
|
||||
```ts type-equiv
|
||||
/** Failed canonical tool execution; failures never carry a successful value. */
|
||||
interface ToolExecutionFailure {
|
||||
readonly isError: true
|
||||
readonly error: ToolFailure
|
||||
readonly value?: never
|
||||
readonly content: ContentBlock[]
|
||||
readonly meta?: JsonValue
|
||||
readonly additionalContexts?: HookContext[]
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** The discriminated, execution-local outcome of one tool call. */
|
||||
type ToolExecutionResult = ToolExecutionSuccess | ToolExecutionFailure
|
||||
```
|
||||
|
||||
结果仅承载产出。调用身份保留在不可变的 `ToolExecution` 上,后者伴随结果经过每个钩子,并出现在持久化的 `tool/call` / `tool/result` 会话事件上,因此包装层无法创建第二个相互矛盾的身份。规范的 `value` 仅存在于执行期间:循环只持久化 `content`、`error` 和 `meta`,`tool/code-dispatch` 则存储有界摘要。回放可以重现展示,却无法重建中间值。
|
||||
|
||||
成功时,注册表会快照并校验函数体返回值,将其冻结,然后调用纯渲染器;对于直接的外层调用,还会调用可选的元数据投影器。注册表会在 `tools/result` 之前另行物化持久展示字段;无效值、渲染器/投影器失败或非 JSON 展示都会转为 JSON 安全的 `isError`。因此,最终实时观察者能看到精确的执行期值,以及可安全用于后续持久追加的字段。
|
||||
|
||||
每个拦截 waterfall 返回一个类型化的 **Decision**(与 `agent/*` seam 共享的惯用模式)。`tools/pre-execute` 监听器接收 `(exec, next)` 并返回 `PreToolDecision`;`tools/execute` 包装层返回 `ToolExecutionResult`;`tools/post-execute` 监听器接收 `(exec, result, next)` 并返回 `PostToolDecision`:
|
||||
|
||||
@@ -300,67 +322,70 @@ type PreToolDecision =
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Post-dispatch decision: accept or replace content, attach context for the next
|
||||
* request, or block by turning corrective feedback into an error result.
|
||||
* Post-dispatch decision: accept, replace one projection, attach context for the
|
||||
* next request, or block by turning corrective feedback into an error result.
|
||||
*/
|
||||
type PostToolDecision =
|
||||
| { kind: 'accept'; content?: ContentBlock[]; additionalContexts?: HookContext[] }
|
||||
| { kind: 'accept'; content?: ContentBlock[]; value?: never; additionalContexts?: HookContext[] }
|
||||
| { kind: 'accept'; value: JsonValue; content?: never; additionalContexts?: HookContext[] }
|
||||
| { kind: 'block'; feedback: ContentBlock[]; additionalContexts?: HookContext[] }
|
||||
```
|
||||
|
||||
调用 `next()` 获取默认决策,或直接返回一个决策以短路。前置策略可以 deny 或 ask;只有 `allowed-once` 才继续执行,而未授权、缺少审批通道或服务、或无 agent 的请求都会变为拒绝。Guard 仍可施加最终拒绝。参数不可被改写,因为历史记录、审计、UI 和执行必须保持一致。
|
||||
|
||||
后置策略可以替换内容;阻止决策会变为包含纠正反馈的 `isError` 结果。`tools/result` 在归一化后接收冻结的执行和结果;观察者无法对其进行变换,观察者的失败也会被隔离。未知工具和抛出异常的工具都会变为结构化错误(`ToolNotFoundError` 映射为 `UNKNOWN_TOOL`),调用失败但不终止当前轮次。
|
||||
后置策略可以替换内容或值,但不能同时替换两者。替换内容会保留规范值和现有元数据;替换值会重新校验并重新计算内容/元数据;阻止会移除值,并转为包含纠正反馈的 `isError`。内容替换是展示策略,而非保密策略;需要隐藏程序化值的监听器必须阻止或替换该值。`tools/result` 在归一化后接收冻结的执行和结果;观察者无法对其进行变换,观察者的失败也会被隔离。未知工具和抛出异常的工具都会变为结构化错误(`ToolNotFoundError` 映射为 `UNKNOWN_TOOL`),调用失败但不终止当前轮次。
|
||||
|
||||
## 结构化输出 schema 子集
|
||||
## 已强制执行的原始 JSON Schema 子集
|
||||
|
||||
调用方用来向 subagent 要求机器可读结果的词汇(`SubagentStartRequest.outputSchema`,见 [subagent.md](subagent.md#the-start-request)),或工作流 `agent()` 调用使用的词汇。它有意不是完整的 JSON Schema:schema 原样传给模型作为强制工具的 `parameters`,产出的值由 `validateStructuredValue` 在客户端校验——因此每个被接受的关键字都必须是校验器实际执行的,`assertSupportedOutputSchema` 会大声拒绝其他任何内容(`OutputSchemaError`,列出所有违规项)。两个遍历器仅推理自有可枚举属性(JSON 不携带其他内容),并拒绝会有损序列化的非纯对象(`Date`、`Map`)。
|
||||
subagent、工作流、MCP 和动态注册提供的原始 schema 使用作者侧 DSL 在协议层的对应表示。`assertSupportedJsonSchema()` 接受任意 JSON 根,`validateJsonSchemaValue()` 强制执行该 schema,`JsonSchemaError` 则报告每条不受支持或格式错误的 schema 路径。仅含注解的空节点表示不受约束的无损 JSON。`oneOf` 至少要求两个分支,且一个值必须恰好匹配其中一个。仍要求对象根的消费方调用 `assertObjectJsonSchema()` 并携带 `ObjectJsonSchema`;这样,subagent/工作流中由调用方定义的结构化输出可以继续以对象为根,而不会限制共享词汇。
|
||||
|
||||
```ts type-equiv
|
||||
/** The scalar values `enum`/`const` may carry (finite numbers only). */
|
||||
type StructuredScalar = string | number | boolean | null
|
||||
/** Scalar JSON values supported by `enum` and `const`. */
|
||||
type JsonSchemaScalar = string | number | boolean | null
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** The `type` keywords the subset accepts. */
|
||||
type StructuredSchemaType = 'object' | 'array' | 'string' | 'number' | 'integer' | 'boolean' | 'null'
|
||||
/** Single-type keywords accepted by the enforced subset. */
|
||||
type JsonSchemaType = 'object' | 'array' | 'string' | 'number' | 'integer' | 'boolean' | 'null'
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* One node of the structured-output schema subset. Recursive via `properties`
|
||||
* and `items`; see the module doc for the exact keyword semantics.
|
||||
* One raw JSON Schema node in the enforced subset. The optional fields express
|
||||
* the external wire shape; {@link assertSupportedJsonSchema} rejects invalid
|
||||
* combinations before a caller treats the node as trusted.
|
||||
*/
|
||||
interface StructuredSchemaNode {
|
||||
type: StructuredSchemaType
|
||||
interface JsonSchemaNode {
|
||||
/** Omit with no constraints for any JSON value, or use `oneOf`. */
|
||||
type?: JsonSchemaType
|
||||
/** Exactly one branch must validate; at least two branches are required. */
|
||||
oneOf?: JsonSchemaNode[]
|
||||
/** Nested property schemas (`type: 'object'` only). */
|
||||
properties?: Record<string, StructuredSchemaNode>
|
||||
properties?: Record<string, JsonSchemaNode>
|
||||
/** Required property names; each must appear in `properties`. */
|
||||
required?: string[]
|
||||
/** `false` rejects undeclared keys; absent/`true` allows them (JSON Schema default). */
|
||||
/** `false` rejects undeclared keys; absent/`true` follows JSON Schema's open default. */
|
||||
additionalProperties?: boolean
|
||||
/** Item schema (`type: 'array'` only); absent ⇒ any JSON items. */
|
||||
items?: StructuredSchemaNode
|
||||
/** Allowed values (scalar types only). */
|
||||
enum?: StructuredScalar[]
|
||||
/** The single allowed value (scalar types only). */
|
||||
const?: StructuredScalar
|
||||
/** Item schema (`type: 'array'` only); absent accepts any JSON item. */
|
||||
items?: JsonSchemaNode
|
||||
/** Allowed values for a scalar node. */
|
||||
enum?: JsonSchemaScalar[]
|
||||
/** The single allowed value for a scalar node. */
|
||||
const?: JsonSchemaScalar
|
||||
/** Annotation, ignored for validation. */
|
||||
description?: string
|
||||
/** Annotation, ignored for validation. */
|
||||
title?: string
|
||||
/** Annotation, ignored for validation (must still be JSON data). */
|
||||
default?: unknown
|
||||
/** Annotation, ignored for validation (must still be JSON data). */
|
||||
examples?: unknown
|
||||
/** Annotation, ignored for validation but required to be lossless JSON. */
|
||||
default?: JsonValue
|
||||
/** Annotation, ignored for validation but required to be lossless JSON. */
|
||||
examples?: JsonValue
|
||||
}
|
||||
```
|
||||
|
||||
schema 是一个以 object 为根的节点(`enum`/`const` 仅限标量;`description`/`title`/`default`/`examples` 是注解,允许但忽略,但仍要求为 JSON 数据——它们随协议传输):
|
||||
|
||||
```ts type-equiv
|
||||
/** A structured-output schema: an OBJECT-rooted {@link StructuredSchemaNode}. */
|
||||
type StructuredOutputSchema = StructuredSchemaNode & { type: 'object' }
|
||||
/** A consumer-constrained object-rooted schema. */
|
||||
type ObjectJsonSchema = JsonSchemaNode & { type: 'object' }
|
||||
```
|
||||
|
||||
## 工具展示 UI 词汇
|
||||
|
||||
@@ -1487,6 +1487,11 @@
|
||||
"symbol": "SessionEventTrace",
|
||||
"source": "packages/session-query/session-query/src/types.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/core-data-structures/tools.zh.md",
|
||||
"symbol": "ToolOutputDefinition",
|
||||
"source": "packages/core/tools/src/index.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/core-data-structures/tools.zh.md",
|
||||
"symbol": "ToolDefinition",
|
||||
@@ -1494,12 +1499,22 @@
|
||||
},
|
||||
{
|
||||
"doc": "docs/core-data-structures/tools.zh.md",
|
||||
"symbol": "SchemaProp",
|
||||
"symbol": "ValueSchemaSpec",
|
||||
"source": "packages/core/tools/src/schema.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/core-data-structures/tools.zh.md",
|
||||
"symbol": "SchemaSpec",
|
||||
"symbol": "ParameterPropertySpec",
|
||||
"source": "packages/core/tools/src/schema.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/core-data-structures/tools.zh.md",
|
||||
"symbol": "ParameterSchemaSpec",
|
||||
"source": "packages/core/tools/src/schema.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/core-data-structures/tools.zh.md",
|
||||
"symbol": "InferValue",
|
||||
"source": "packages/core/tools/src/schema.ts"
|
||||
},
|
||||
{
|
||||
@@ -1547,6 +1562,21 @@
|
||||
"symbol": "ToolRestriction",
|
||||
"source": "packages/core/tools/src/index.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/core-data-structures/tools.zh.md",
|
||||
"symbol": "ToolFailure",
|
||||
"source": "packages/core/tools/src/index.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/core-data-structures/tools.zh.md",
|
||||
"symbol": "ToolExecutionSuccess",
|
||||
"source": "packages/core/tools/src/index.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/core-data-structures/tools.zh.md",
|
||||
"symbol": "ToolExecutionFailure",
|
||||
"source": "packages/core/tools/src/index.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/core-data-structures/tools.zh.md",
|
||||
"symbol": "ToolExecutionResult",
|
||||
@@ -1564,22 +1594,22 @@
|
||||
},
|
||||
{
|
||||
"doc": "docs/core-data-structures/tools.zh.md",
|
||||
"symbol": "StructuredScalar",
|
||||
"symbol": "JsonSchemaScalar",
|
||||
"source": "packages/core/tools/src/json-schema.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/core-data-structures/tools.zh.md",
|
||||
"symbol": "StructuredSchemaType",
|
||||
"symbol": "JsonSchemaType",
|
||||
"source": "packages/core/tools/src/json-schema.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/core-data-structures/tools.zh.md",
|
||||
"symbol": "StructuredSchemaNode",
|
||||
"symbol": "JsonSchemaNode",
|
||||
"source": "packages/core/tools/src/json-schema.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/core-data-structures/tools.zh.md",
|
||||
"symbol": "StructuredOutputSchema",
|
||||
"symbol": "ObjectJsonSchema",
|
||||
"source": "packages/core/tools/src/json-schema.ts"
|
||||
},
|
||||
{
|
||||
@@ -1717,6 +1747,11 @@
|
||||
"symbol": "ConfinedArgv",
|
||||
"source": "packages/sandbox/sandbox/src/index.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/core-data-structures/code-runtime.zh.md",
|
||||
"symbol": "CodeJsonValue",
|
||||
"source": "packages/code-runtime/code-runtime/src/types.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/core-data-structures/code-runtime.zh.md",
|
||||
"symbol": "CodeRunRequest",
|
||||
@@ -1732,6 +1767,11 @@
|
||||
"symbol": "CodeBindingNamespace",
|
||||
"source": "packages/code-runtime/code-runtime/src/types.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/core-data-structures/code-runtime.zh.md",
|
||||
"symbol": "CodeBindingErrorClass",
|
||||
"source": "packages/code-runtime/code-runtime/src/types.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/core-data-structures/code-runtime.zh.md",
|
||||
"symbol": "CodeBindingFunction",
|
||||
|
||||
Reference in New Issue
Block a user