@jangts/agent-core 0.2.0 → 0.2.5
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/CHANGELOG.md +14 -0
- package/LICENSE +55 -0
- package/NOTICE +6 -0
- package/README.md +8 -0
- package/development/docs/README.en.md +70 -0
- package/development/docs/README.md +1 -1
- package/development/docs/acceptance.en.md +34 -0
- package/development/docs/agent-development.en.md +42 -0
- package/development/docs/api/README.en.md +62 -0
- package/development/docs/api/advanced-agents.en.md +135 -0
- package/development/docs/api/context-processing.en.md +110 -0
- package/development/docs/api/core.en.md +274 -0
- package/development/docs/api/examples.en.md +118 -0
- package/development/docs/api/kits.en.md +183 -0
- package/development/docs/api/knowledge-answering.en.md +166 -0
- package/development/docs/api/mock.en.md +186 -0
- package/development/docs/api/observability.en.md +166 -0
- package/development/docs/api/reference.en.md +2235 -0
- package/development/docs/api/reference.md +290 -257
- package/development/docs/api/support.en.md +252 -0
- package/development/docs/api/types.en.md +181 -0
- package/development/docs/api/workflow.en.md +198 -0
- package/development/docs/api/workflow.md +22 -0
- package/development/docs/basic-usage.en.md +78 -0
- package/development/docs/create-app.en.md +76 -0
- package/development/docs/examples/offline-agent.en.md +273 -0
- package/development/docs/examples.en.md +36 -0
- package/development/docs/flows/knowledge-maintenance.en.md +38 -0
- package/development/docs/getting-started.en.md +71 -0
- package/development/docs/knowledge.en.md +89 -0
- package/development/docs/maintenance.en.md +62 -0
- package/development/docs/materials.en.md +38 -0
- package/development/docs/mechanisms.en.md +227 -0
- package/development/docs/memory-and-learning.en.md +42 -0
- package/development/docs/operations.en.md +51 -0
- package/development/docs/records.en.md +63 -0
- package/development/docs/registration.en.md +63 -0
- package/development/skills/agent-core-app/SKILL.md +4 -0
- package/development/skills/agent-core-app/scripts/create-app.mjs +19 -0
- package/development/skills/agent-core-app/scripts/lib/knowledge.mjs +37 -1
- package/development/skills/agent-core-app/scripts/plan.mjs +9 -0
- package/development/skills/agent-core-app/scripts/resources.mjs +17 -0
- package/dist/cjs/diagnostics/catalog.js +6 -0
- package/dist/cjs/execution/cache.js +162 -0
- package/dist/cjs/execution/engine.js +10 -0
- package/dist/cjs/kits/vector-search.js +4 -7
- package/dist/cjs/structs/execution/cache.js +6 -0
- package/dist/cjs/support/model/chat-completions.js +1 -1
- package/dist/cjs/support/model/completion/default.js +1 -0
- package/dist/cjs/support/model/completion/session.js +5 -0
- package/dist/esm/diagnostics/catalog.d.ts +6 -0
- package/dist/esm/diagnostics/catalog.js +6 -0
- package/dist/esm/execution/cache.d.ts +20 -0
- package/dist/esm/execution/cache.js +156 -0
- package/dist/esm/execution/engine.d.ts +5 -0
- package/dist/esm/execution/engine.js +10 -0
- package/dist/esm/kits/vector-search.js +4 -7
- package/dist/esm/structs/execution/cache.d.ts +54 -0
- package/dist/esm/structs/execution/cache.js +5 -0
- package/dist/esm/structs/execution/engine.d.ts +6 -1
- package/dist/esm/structs/execution/workflow.d.ts +2 -1
- package/dist/esm/structs/index.d.ts +1 -0
- package/dist/esm/structs/kits/retrieval.d.ts +1 -0
- package/dist/esm/structs/kits/search.d.ts +2 -0
- package/dist/esm/structs/model/messages.d.ts +4 -0
- package/dist/esm/structs/support/model/messages.d.ts +2 -0
- package/dist/esm/support/context/nodes.d.ts +16 -0
- package/dist/esm/support/context/processing.d.ts +7 -0
- package/dist/esm/support/model/chat-completions.js +1 -1
- package/dist/esm/support/model/children.d.ts +2 -0
- package/dist/esm/support/model/completion/default.d.ts +1 -0
- package/dist/esm/support/model/completion/default.js +1 -0
- package/dist/esm/support/model/completion/session.d.ts +5 -0
- package/dist/esm/support/model/completion/session.js +5 -0
- package/dist/esm/support/model/preview-rules.d.ts +6 -0
- package/dist/esm/support/model/reply-preview.d.ts +7 -0
- package/dist/esm/support/model/strategies.d.ts +4 -0
- package/package.json +7 -4
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# Support: assembling against shared contracts
|
|
2
|
+
|
|
3
|
+
[Shared types](types.md) · [Back to home](README.md). Support is used from the root namespace and provides implementations that conform to Core interfaces.
|
|
4
|
+
|
|
5
|
+
## SchemaAdapter
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
Support.defineSchema<Value>({ jsonSchema, parse }): SchemaAdapter<Value>
|
|
9
|
+
Support.fromJsonSchema(jsonSchema, { compile, error? }): SchemaAdapter<unknown>
|
|
10
|
+
Support.fromParserSchema(schema, { toJsonSchema }): SchemaAdapter<Output>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
defineSchema accepts an explicit raw schema and parser, copies jsonSchema, and fixes the parser reference and receiver. fromJsonSchema reuses the App's validator/compiler; fromParserSchema connects existing parser libraries such as Zod and does not maintain a second JSON Schema.
|
|
14
|
+
|
|
15
|
+
Schema compilation, conversion, validation, parsing, and error factories are all synchronous contracts; an unexpected Promise/thenable return immediately throws TypeError and observes the late rejection. `compile` must return a validator function when the adapter is created, and the validator must return a boolean at runtime.
|
|
16
|
+
|
|
17
|
+
A bare JSON Schema provides no TypeScript business type proof, and fromJsonSchema returns unknown by default; when Value inference is needed, use a parser that returns a validated Value or a typed compiler port, and never pass an arbitrary <Value> assertion off as type inference.
|
|
18
|
+
|
|
19
|
+
All three functions return the same SchemaAdapter structure; the mode is not guessed from whether inputSchema happens to contain parse/jsonSchema fields. The parser is synchronous; asynchronous business reads and authorization belong in the execution handler.
|
|
20
|
+
|
|
21
|
+
## Tool declarations and catalogs
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
interface ToolDefinition<Input, Output, App> {
|
|
25
|
+
readonly id: string;
|
|
26
|
+
readonly kind: string;
|
|
27
|
+
readonly description: string;
|
|
28
|
+
readonly inputSchema: SchemaAdapter<Input>;
|
|
29
|
+
authorize(input: Input, context: OperationContext<App>): MaybePromise<void>;
|
|
30
|
+
execute(input: Input, context: OperationContext<App>): MaybePromise<Output>;
|
|
31
|
+
parseOutput(value: unknown): Output;
|
|
32
|
+
}
|
|
33
|
+
Support.defineTool<Input, Output, App>(definition): ToolDefinition<Input, Output, App>
|
|
34
|
+
Support.defineToolCatalog(definitions): ToolCatalog
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
authorize returns void on approval and throws on rejection. When adapting existing boolean permission functions, evaluate the result inside the callback and throw the App's authorization error. Support fixes methods and receivers at registration and validates the completion value of asynchronous authorization. parseOutput parses the tool execution result.
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
catalog.select(toolIds): ToolCatalog<SelectedDefinitions>
|
|
41
|
+
catalog.bind({ app, execution }): ToolRuntime
|
|
42
|
+
runtime.invoke({ toolId, input }): Promise<ToolResult<InferredOutput>>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
catalog.capabilities projects the same jsonSchema from the tool declarations; its capability adapter checks only the raw parameter object and the selection contract, resolve returns `{ toolId, input }`, and input keeps the raw parameters. It does not run the tool's business inputSchema.parse early; the tool parses once in the real invoke before authorization and execution, avoiding repeated runs of transforming parsers. A custom model stage may register its own parsing and resolve, but a converted value must not be passed off again as the tool's raw input.
|
|
46
|
+
|
|
47
|
+
Catalog implements the shared read-only collection interface. After a static selection, the inferred tool IDs and result types narrow together; dynamic IDs go through the unknown boundary. Context/App do not enter the model schema and cannot be overridden by model parameters.
|
|
48
|
+
|
|
49
|
+
The return shape is always `{ value, invocation }`, with the actual tool result in value; execute/parseOutput handle only business values and do not construct call receipts themselves.
|
|
50
|
+
|
|
51
|
+
runtime is created per request; all input, authorize, execute, and output callbacks receive or use the same effective signal. Cancellation, unknown tools, and input or output rejection can never write a success receipt. A static schema description by itself does not prove execution.
|
|
52
|
+
|
|
53
|
+
The same binding provides asynchronous `invoke` and explicit synchronous `invokeSync`. Both reuse the same Core registry, authorization checks, and receipt recording.
|
|
54
|
+
|
|
55
|
+
## App capability composition
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
Support.defineCapabilityCatalog(definitions, { toolReferences? }): CapabilityCatalog
|
|
59
|
+
Support.validateToolReferences(toolIds, references): void
|
|
60
|
+
Support.defineRegistrationCatalog(definitions): RegistrationCatalog
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Modules use id, description, and tools. The primary key of a tools item is id; reference explicitly states requiredToolIds/optionalToolIds. The composed result inherits the shared Catalog operations and provides tools and findToolOwner(toolId); an unknown owner returns undefined.
|
|
64
|
+
|
|
65
|
+
The composition layer only verifies references and assembly and does not understand the App's business completion or recovery fields. RegistrationCatalog uses a definitions array, and install(app) registers in declaration order under when conditions. Static registration, deployment configurability, and per-request authorization are judged separately; a single enabled flag does not replace all three.
|
|
66
|
+
|
|
67
|
+
## Model transports and stages
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
Support.createTextTransport({ id, options? }): ModelTransportDefinition<string>
|
|
71
|
+
Support.createJsonTransport({ id, outputSchema, options? }): ModelTransportDefinition<Output>
|
|
72
|
+
Support.createModelTransport({ id, capabilities, adapter?, options?, requireStrictArguments? }): ModelTransportDefinition
|
|
73
|
+
Support.createStageModelTransport({ id, capabilities, stages, adapter?, options? }): ModelTransportDefinition
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`createJsonTransport` is used to generate JSON values that conform to a Schema directly, such as summaries, classification results, or tabular data. `outputSchema` is an ordinary `SchemaAdapter<Output>` containing `jsonSchema` and a synchronous `parse(value)`; it can be provided directly, built with a Support Schema factory, or passed as a stateful class implementation. The parser is called after JSON decoding; declaring a JSON Schema does not replace runtime validation.
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const summary = Support.createJsonTransport({ id: 'summary', outputSchema: {
|
|
80
|
+
jsonSchema: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'], additionalProperties: false },
|
|
81
|
+
parse(value: unknown) {
|
|
82
|
+
if (!value || typeof value !== 'object' || !('text' in value) || typeof value.text !== 'string') throw new TypeError('需要摘要正文');
|
|
83
|
+
return { text: value.text };
|
|
84
|
+
},
|
|
85
|
+
} });
|
|
86
|
+
const model = new ModelRunner({ provider, transports: [summary] });
|
|
87
|
+
const result = await model.invoke({ input: '概括资料', transport: summary });
|
|
88
|
+
console.log(result.value.text, result.invocation);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The ID keeps its literal type; after registration the original transport handle or the ID can be used for calls. Each call creates a new collector, and parts accumulate only within that call. Invalid JSON, Schema parser rejection, asynchronous parsers, and function-call parts all propagate as failures and produce no deliverable success value. Input/output/Schema budgets, cancellation, and call limits continue to be enforced by the same Runner.
|
|
92
|
+
|
|
93
|
+
It requires no capability list and introduces no capability/input envelope; when a model must choose among registered capabilities, use `createModelTransport` or `createStageModelTransport`. The JSON request mode and the JSON value types a provider actually supports are decided by the Provider. Output remains uniformly `{ value, invocation }` and does not become a bare object in JSON mode. `options` accepts only temperature overrides and does not relax Provider and Runner constraints.
|
|
94
|
+
|
|
95
|
+
capabilities is a ModelCapabilitySource or a source factory read by stageId. A stages item uses `{ id, capabilityIds }`. Static sources validate all references at assembly, and dynamic sources refresh when the interaction is prepared for this call. An unknown stage does not invoke a dynamic source and does not extend to other stages.
|
|
96
|
+
|
|
97
|
+
prepare(stageId) returns the unified collector protocol; a prepared interaction fixes the resolver and receiver for this call, and the next dynamic preparation can obtain a new source. Native and JSON adapters consume the same capability projection. The native adapter validates the object shape, index, ID, name, and argument strings of neutral function-call-delta values; invalid parts do not enter capability execution. The collector's outputChars counting and call metadata reading boundaries follow the [Core transport contract](core.md). Fallback is chosen explicitly by the App and is not guessed implicitly from model names or URLs.
|
|
98
|
+
|
|
99
|
+
## Model strategy and budget
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
Support.createModelStrategy({ model, models?, reasoning?, reasoningOptions?, budget?, ...coreSettings }): ModelRunnerOptions
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`model` accepts a model definition or an ID in `models`. `reasoning` selects a configuration explicitly registered in `reasoningOptions`; for example, high/off correspond to a Provider already configured with provider reasoning parameters. Unknown IDs or reasoning schemes raise errors, and model capabilities are not inferred from names.
|
|
106
|
+
|
|
107
|
+
The budget defaults to medium. These are overridable local task budgets, not guarantees of provider capacity or performance:
|
|
108
|
+
|
|
109
|
+
| budget | Scenario | maxCalls | maxInputChars | maxOutputChars | maxOutputTokens | callTimeoutMs |
|
|
110
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
111
|
+
| small | Short task with few calls | 2 | 8000 | 4000 | 512 | 15000 |
|
|
112
|
+
| medium | Ordinary multi-step task | 8 | 32000 | 16000 | 1024 | 30000 |
|
|
113
|
+
| large | More calls and output space | 16 | 64000 | 32000 | 4096 | 60000 |
|
|
114
|
+
|
|
115
|
+
Values apply from lowest to highest: the tier, the model definition, the reasoning configuration, exact values in the budget object, and top-level Core settings. Legitimate 0 and Infinity are preserved; an explicit undefined uses the Core default. A budget object uses medium as its baseline. All Core model fields can be passed, and the output is still the same ModelRunnerOptions. A result without a bound execution can be used directly as an Engine's model; request-level execution still belongs to the Engine and cannot be bypassed through a strategy.
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const settings = Support.createModelStrategy({ model: { provider }, budget: 'small', maxCalls: 1 });
|
|
119
|
+
const model = new ModelRunner(settings);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Context
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
Support.defineContextFragments(definitions, options?): ContextBuilder
|
|
126
|
+
Support.defineContextAssembly(definitions, options): ContextBuilder
|
|
127
|
+
builder.fragments(input): readonly ContextFragment[]
|
|
128
|
+
builder.plan({ input, maxInputChars?, originalFragments?, mutableFragmentIds?, additionalMessages? }): ContextPlan
|
|
129
|
+
builder.build({ input, maxInputChars?, originalFragments?, mutableFragmentIds?, additionalMessages? }): BuiltContext
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
- ContextPlan.fragments are unassembled definition fragments; BuiltContext.fragmentUsage is used-amount metadata and must not also be called fragments.
|
|
133
|
+
- A fragment uses id, sourceId, trust, lifetime, maxChars, role, and value. Business original text is data and is not promoted to a system instruction.
|
|
134
|
+
- Formatting first obtains this call's input, then assembles by declaration order and budget; the current question, authorization conditions, and existing evidence cannot be trimmed at will.
|
|
135
|
+
- ContextBuilder has no per-request signal. Selection and compression run as plain Workflow nodes, and their internal calls reuse the Core request budget and signal.
|
|
136
|
+
|
|
137
|
+
Support strategy modes are `validate | reduce | audit`. `createContextProcessingNode` returns a plain WorkflowNode; when observation and routing must be separate, use `createContextSelectionNode` and `createContextReductionNode`. Core does not schedule by these modes; for concrete run boundaries, see [Context nodes](context-processing.md).
|
|
138
|
+
|
|
139
|
+
`createContextStrategy` can use `preset: compact | balanced | extended` to generate a target character count at 25%/50%/75% of the fragment hard budget and keep the most recent 1/3/6 groups of original text; exact targetChars and keepGroups take precedence. The ratio expresses only character space, not semantic retention. Without a preset, the fragment hard budget and one group of original-text protection apply. The function can generate a recent-window or continuous-task summary configuration from registered historical fragments. It reuses fragment IDs/budgets, generation step IDs, and handler references; after exact overrides of targetChars, keepGroups, grouping, and selection it still uses the same node contract. The returned policy, compressors, and selectors can be configured directly into a plain processing node. A single strategy can configure the full selection (including model), selectors, compressors, steps, and character/token triggers and target watermarks; explicit steps fully replace generation steps and their dependencies; when steps are given directly, the generation-specific kind, fragment, field, and compressor can be omitted. Default selection is decided by the final step: fixed for a single step, sequence for multiple steps. When an array of strategies is passed, each item generates an operation, shared settings go in the second parameter, and execution defaults to sequential order. Duplicate steps or conflicting handlers are rejected at assembly. Each group defaults to user-turn and the most recent group's complete original text is kept; the numbers come from configuration and complete-round protection and do not promise model performance or summary quality.
|
|
140
|
+
|
|
141
|
+
The budget can be configured once at assembly. Pass a number for a fixed budget, or `maxInputChars(input)` for a per-request calculation.
|
|
142
|
+
Both forms use `builder.build({ input })` or `builder.plan({ input })`, adding a budget only for a single-call override.
|
|
143
|
+
If no budget is configured or passed with the request, construction raises an error. `plan` and `build` both accept the additional messages and change-audit fields of the Core ContextPlan; user messages with images participate in the final budget, and build reuses Core auditing and assembly without silently dropping these fields. Processing entry points accept the same character budgets as Core (including Infinity); a zero budget must still pass the final actual usage check. Each processing call first fixes the plan and the contract fields of structured fragments, messages, and images, allows prototype getters, and does not let caller changes to the original plan during asynchronous processing alter an already-determined budget.
|
|
144
|
+
|
|
145
|
+
## Provider, previews, and streams
|
|
146
|
+
|
|
147
|
+
`CompatibleModelProvider.stream` also enforces the cancellation contract when used directly: an already-cancelled call sends no HTTP request, and waiting during the request/SSE can be stopped; if a request that ignores cancellation returns late, the adapter releases its unconsumed body. Body cleanup for HTTP or format errors is best-effort and does not overwrite the original classification; diagnostic body capture still has independent size and time limits and releases the reader.
|
|
148
|
+
|
|
149
|
+
`writeSseFrame` requires `writer.write` to return a boolean synchronously; waiting for drain is protected by the unified cancellation wait. `readSse` checks cancellation before obtaining a reader and between events within a network chunk, and does not deliver buffered late events.
|
|
150
|
+
|
|
151
|
+
| Interface | Preferred input / semantics |
|
|
152
|
+
| --- | --- |
|
|
153
|
+
| new CompatibleModelProvider | `{ config, ports?, observe?, errors? }`, where config accepts static configuration or a dynamic reader function |
|
|
154
|
+
| createReplyPreviewRules | generates and validates preview rules from actual stage references, capability Schemas, and explicit display eligibility |
|
|
155
|
+
| createReplyPreview | `{ rules, sink, render?, maxChars? }`, returning a handler for the model output sink to call |
|
|
156
|
+
| streamEvents | `(producer, context)`, where producer must await emit and maintain bounded backpressure |
|
|
157
|
+
| readSse | `(body, options)`, with signal and idleTimeoutMs in options, following real transport constraints |
|
|
158
|
+
| encodeSseEvent | raw SSE event structure, preserving external event/data fields |
|
|
159
|
+
| writeSseFrame | `({ writer, frame }, context)`, awaiting backpressure and propagating delivery errors |
|
|
160
|
+
| scanJsonObjectFrames / parseJsonFrames | stay synchronous; the former only splits, the latter parses and limits the frame count |
|
|
161
|
+
|
|
162
|
+
modelInteractionCatalog selects a native function or JSON adapter by an explicitly registered interaction ID; the App supplies selection metadata, can override explicitly, and does not infer capability from brand, model name, or URL.
|
|
163
|
+
|
|
164
|
+
Preview rules use stageId and capabilityId; previews are transient and cannot authorize, persist, or execute model proposals. ModelOutputEvent goes to the sink and ProviderObservation goes to the observer; they are not mixed.
|
|
165
|
+
|
|
166
|
+
External SSE/wire types can keep standard fields and are converted at the Support boundary; the protocol itself is not changed to standardize SDK spelling.
|
|
167
|
+
|
|
168
|
+
The compatible Provider's `requestBody` is for provider extension fields. Model identity, input messages, streaming protocol, and token limits are controlled by the adapter and Core call contracts. Overriding these fields raises an error before the request is sent. To use another request protocol, implement the same `ChatProvider` interface directly.
|
|
169
|
+
|
|
170
|
+
Completion determination is selected through `ports.completionPolicy` and defaults to `Support.completionPolicyCatalog.default`; whether function parts are accepted, the expected `finish_reason`, and integrity before `[DONE]` are all judged by whether the call declares `functions`, and a stream that ends without a finish marker still ends as `MODEL_INCOMPLETE`. `Support.completionPolicyCatalog.session` judges by the on-the-wire `tool_choice`: `auto` requires every started call to be complete and allows accompanying body text, `required` and a specific function object require at least one complete call, `none` rejects any function part, omitted fields adopt the default completion rules, and unknown values are rejected before sending as configuration errors. A policy only judges whether the response satisfies the request; it does not take over the request loop, transport protection, or tool execution. Cancellation and transport failures never become success because of a policy.
|
|
171
|
+
|
|
172
|
+
`Support.createToolCallCollector` aggregates call parts by index, and the completion policy and direct callers share the same implementation. Conflicting part IDs, duplicate IDs across indices, and incomplete calls are rejected as protocol errors, and one complete call does not mask another incomplete call.
|
|
173
|
+
|
|
174
|
+
The protocol layer never restricts receiving multiple call parts, and `parallel_tool_calls` is only a send field that expresses neither execution authorization nor scheduling policy. The single-proposal limit on the Agent path is decided by the `nativeFunctionInteraction` collector; using the Provider directly is not subject to that limit and does not thereby gain tool execution, authorization, or loop capabilities.
|
|
175
|
+
|
|
176
|
+
`ProviderMessage` provides the `tool` result and assistant `toolCalls` shapes needed for direct connection, and `chatCompletionMessages` maps them explicitly to `tool_call_id` and nested `tool_calls`; mismatched roles or fields and incomplete call fields are rejected as `TypeError`. Only declared fields are sent, and extra fields (including raw wire fields) are not passed through; `toolCallId` must be a non-empty string, and `role` and `content` are validated per the message contract. The Core `ModelMessage` and `ChatProvider` abstract signatures are unchanged.
|
|
177
|
+
|
|
178
|
+
A direct tool thread uses declared fields, the assistant `content` may be an empty string, and tool results are correlated by the same call ID:
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
import { Support } from '@jangts/agent-core';
|
|
182
|
+
|
|
183
|
+
const messages: Support.ProviderMessage[] = [
|
|
184
|
+
{ role: 'user', content: '查询资料' },
|
|
185
|
+
{
|
|
186
|
+
role: 'assistant', content: '',
|
|
187
|
+
toolCalls: [{ id: 'call-1', name: 'lookup', arguments: '{"query":"资料"}' }],
|
|
188
|
+
},
|
|
189
|
+
{ role: 'tool', content: '{"found":true}', toolCallId: 'call-1' },
|
|
190
|
+
];
|
|
191
|
+
const wireMessages = Support.chatCompletionMessages(messages);
|
|
192
|
+
// 工具声明映射为 tool_calls,工具结果映射为 tool_call_id。
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Tool registration, parameter business validation, authorization, and execution are the host's responsibility. `createToolCallCollector.complete()` checks the integrity of call IDs and names and does not parse or validate business argument JSON.
|
|
196
|
+
|
|
197
|
+
When multiple requests reuse the same Provider, `Support.createProviderObservationScope` can bind the App's own output rules. Assign the returned `observer` to the Provider and, for each request, use `attach(diagnostics, destination)` to associate the log destination. Child scopes inherit the association automatically. The App is responsible for redaction, retention, and write policy, and Provider events are recorded through the existing App log outlets.
|
|
198
|
+
|
|
199
|
+
## Rejection checks
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
Support.breakWhile(occur, error?, tags?): void
|
|
203
|
+
Support.breakWhile([
|
|
204
|
+
{ occur: () => expired, error: '已过期' },
|
|
205
|
+
{ occur: [() => missing, () => invalid], error: '输入不可用' },
|
|
206
|
+
]): void
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`occur` specifies rejection conditions: a truthy value, a lazy predicate, or an array of OR predicates that short-circuit in order; any true item rejects, stopping later predicates and error factories. Multiple check objects also stop at the first rejection in order. ProtocolCheck uses the same `occur` field, and `when` decides whether the check is enabled.
|
|
210
|
+
|
|
211
|
+
For complete exports, exact overloads, and declaration sources, see [Public symbol reference](reference.md). Signatures in examples are for reading only; omitted fields cannot be used to infer arbitrary extension options.
|
|
212
|
+
|
|
213
|
+
## Context compression/selection and local callbacks
|
|
214
|
+
|
|
215
|
+
`createContextCompressor({ id, instruction, transport, maxOutputTokens, parse })` and `createContextSelector({ id, instruction?, transport, maxOutputTokens, parse?, maxExcerptChars? })` construct registrable handlers. The main request and their model calls share the same runner, deadline, remaining budget, and parent diagnostic identity:
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
compress(request: ContextCompressionCall, context: ExecutionContext)
|
|
219
|
+
select(request: ContextSelectionCall, context: ExecutionContext)
|
|
220
|
+
request.callModel({ stageId, input, maxOutputTokens, transport }): Promise<ModelResult<unknown>>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
request provides stageId and the fragments to compress or candidates to select; the result parser handles only the returned value. Policies reference handlers by handlerId; callModel takes a named request and returns a ModelResult. The cancellation signal is read from the second context. Processing nodes connect these callbacks through the Workflow model port; for original-text protection, history grouping, watermarks, reserved calls, and audit rules, see [Context nodes](context-processing.md).
|
|
224
|
+
|
|
225
|
+
createReplyPreview uses `{ rules, sink, render?, maxChars? }`; rules use stageId and capabilityId (native adaptation maps the latter to the function wire name). The sink is awaited in order, and render is synchronous. Rule data is copied as a snapshot and callbacks fix the original receiver; a preview never authorizes or executes tools.
|
|
226
|
+
|
|
227
|
+
When a selector omits `instruction`, it uses the built-in trimming selection instruction, which requires the model to return `{ stepIds }` and explicitly treats fragment excerpts as data. A custom parser must provide the corresponding instruction as well.
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
### Explicit reasoning wire policies
|
|
231
|
+
|
|
232
|
+
CompatibleModelProvider's `config.reasoning` accepts `{ format, level }`. level is off/low/medium/high; format explicitly specifies the field format the provider supports and is not guessed from the model name.
|
|
233
|
+
|
|
234
|
+
| format | Generated fields |
|
|
235
|
+
| --- | --- |
|
|
236
|
+
| reasoning-effort | reasoning_effort: none/low/medium/high |
|
|
237
|
+
| thinking | thinking.type: disabled/enabled, switch only |
|
|
238
|
+
| enable-thinking | enable_thinking: false/true, switch only |
|
|
239
|
+
| thinking-budget | thinking.type and budget_tokens; low/medium/high default to 1024/4096/8192, and budgets can override them precisely |
|
|
240
|
+
| custom | map(level) returns custom requestBody fields and must be synchronous |
|
|
241
|
+
|
|
242
|
+
Explicit requestBody fields take precedence over generated fields; neither can override model, messages, stream, or output limits. thinking-budget must reserve space for the answer, and the thinking token count must be less than the actual output limit; otherwise the request is rejected before sending without expanding the Core budget. A mapper with getters or private state keeps its receiver, static configuration is resolved only once, and dynamic configuration is resolved per request.
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
const provider = new Support.CompatibleModelProvider({ config: {
|
|
246
|
+
baseUrl, apiKey, model,
|
|
247
|
+
reasoning: { format: 'thinking-budget', level: 'high' },
|
|
248
|
+
} });
|
|
249
|
+
const settings = Support.createModelStrategy({ model: { provider }, maxOutputTokens: 10000 });
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
JSON value transports enable `json` mode by default. This mode in CompatibleModelProvider uses `json_object`; when an array or scalar is needed and the provider does not support the corresponding mode, set `json: false` explicitly in the same `options` while keeping full JSON decoding and outputSchema validation.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Shared types and behavior contracts
|
|
2
|
+
|
|
3
|
+
[Back to API handbook](README.md). This page describes the base types and invocation rules shared by all modules.
|
|
4
|
+
|
|
5
|
+
## Identity, configuration, and time
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
type Id = string;
|
|
9
|
+
type TimestampMs = number;
|
|
10
|
+
type DurationMs = number;
|
|
11
|
+
type JsonSchema = Readonly<Record<string, unknown>>;
|
|
12
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
These aliases only state meaning. They do not replace runtime validation, and they do not require every App to use branded types. An id must satisfy the constraints of its catalog or adapter; IDs supplied by models and clients are not proof of permission.
|
|
16
|
+
|
|
17
|
+
| Field | Sole meaning |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| id | Primary key of the currently registered entity |
|
|
20
|
+
| *Id / *Ids | Identifiers that point to other entities |
|
|
21
|
+
| name / description | Display name / description of purpose |
|
|
22
|
+
| kind | Domain classification, such as a read-only tool or an output kind |
|
|
23
|
+
| type | Discriminant tag of a message or event |
|
|
24
|
+
| version | Version of a serializable format |
|
|
25
|
+
| revision | Revision identity of content or a declaration |
|
|
26
|
+
| identity | Real model or space identity behind a registered alias, not merged with id |
|
|
27
|
+
| *AtMs / *Ms | Absolute millisecond time / duration |
|
|
28
|
+
| *Index / *Sequence / *Count | 0-based index / 1-based sequence / cumulative quantity |
|
|
29
|
+
|
|
30
|
+
`Definition` is a reusable declaration; `Config` is a configuration value; `Ports` are execution dependencies; `Options` are construction or invocation options; `State` is workflow state; `Runtime` is an instance with a bound lifecycle. A single request's signal must not be placed in a reusable Definition, and numeric budgets must not be hidden in Ports.
|
|
31
|
+
|
|
32
|
+
## Execution context
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
interface ExecutionOptions {
|
|
36
|
+
readonly signal?: AbortSignal;
|
|
37
|
+
readonly diagnostics?: DiagnosticContext;
|
|
38
|
+
}
|
|
39
|
+
interface OperationContext<App> {
|
|
40
|
+
readonly app: Readonly<App>;
|
|
41
|
+
readonly signal: AbortSignal;
|
|
42
|
+
readonly diagnostics?: DiagnosticContext;
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Core supplies the actual signal when it creates a request instance; the signal received by a registered handler always exists. `app` is the business dependency supplied by the caller, and models cannot override it. A Kit may define a local execution context that extends this contract, but it cannot require other Kits to fill its fields.
|
|
47
|
+
|
|
48
|
+
- A request instance binds execution at construction, so later invoke calls do not need to pass signal again.
|
|
49
|
+
- Reusable capabilities receive context on every run/read/resolve.
|
|
50
|
+
- App service identity, budget, and cancellation travel along the real call chain; nesting a Kit never grants new budget.
|
|
51
|
+
|
|
52
|
+
## Schema and parsing
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
interface SchemaAdapter<Value> {
|
|
56
|
+
readonly jsonSchema: JsonSchema;
|
|
57
|
+
parse(input: unknown): Value;
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`inputSchema` uses SchemaAdapter and contains an executable parse plus a read-only jsonSchema; only jsonSchema is projected to the model. parse runs synchronously; a Promise returned by mistake is rejected and the late rejection is handled. A Promise must never be treated as a parsed value.
|
|
62
|
+
|
|
63
|
+
Context-dependent permission and protocol refinements belong to named handlers; stage, source, or permission checks must not be deleted just to fit them into a pure schema.
|
|
64
|
+
|
|
65
|
+
## Booleans, assertions, checks, and authorization
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
interface Issue {
|
|
69
|
+
readonly code: string;
|
|
70
|
+
readonly message: string;
|
|
71
|
+
readonly path?: readonly (string | number)[];
|
|
72
|
+
readonly checkId?: string;
|
|
73
|
+
}
|
|
74
|
+
interface CheckResult<Value = never> {
|
|
75
|
+
readonly status: 'passed' | 'failed' | 'unavailable' | 'not-run';
|
|
76
|
+
readonly issues: readonly Issue[];
|
|
77
|
+
readonly value?: Value;
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
| Contract | Returns | Failure / side effects |
|
|
82
|
+
| --- | --- | --- |
|
|
83
|
+
| `isX(input)` | boolean | Performs no conversion; false is only a negation |
|
|
84
|
+
| `assertX(input)` | void | Throws on invalid input; false must not be used to express rejection |
|
|
85
|
+
| `parseX(input)` | Value | Validates and converts; throws on failure |
|
|
86
|
+
| `checkX(input, context)` | CheckResult | Check issues may be returned as a report; program errors still throw |
|
|
87
|
+
| `authorize(input, context)` | void or Promise<void>, decided by the corresponding execution interface | Throws on rejection; synchronous entry points do not allow thenables |
|
|
88
|
+
|
|
89
|
+
A passed result has empty issues; failed has at least one Issue; unavailable/not-run must not be treated as passed. value replaces the original only when an own-property check confirms it was provided (including an explicit undefined); truthiness must not be used to test presence. Check status does not replace tool execution status, task status, or delivery status.
|
|
90
|
+
|
|
91
|
+
Boolean permission tests are named isAllowed. TypeScript allows functions that return non-void values to be assigned to void callbacks, so executors check the actual completion value of authorize/assert: only undefined means approval; false, true, or any other value is a contract error. Asynchronous entry points check after await. When adapting a boolean permission test, approval returns undefined and rejection throws.
|
|
92
|
+
|
|
93
|
+
A synchronous entry point rejects on detecting a thenable and consumes its late rejection so it does not become an unhandled rejection; this does not roll back external side effects that already occurred, so asynchronous authorization and I/O must use asynchronous entry points.
|
|
94
|
+
|
|
95
|
+
## Invocation results: fixed shape
|
|
96
|
+
|
|
97
|
+
A single invocation API can have only one success return shape. Input shorthand only normalizes; it must not return a string for one input and an object for another, and no option may switch return paradigms through fullResponse, raw, or input shape.
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
interface InvocationMetadata {
|
|
101
|
+
readonly runId?: string;
|
|
102
|
+
readonly spanId?: string;
|
|
103
|
+
readonly parentSpanId?: string;
|
|
104
|
+
readonly invocationId: string;
|
|
105
|
+
readonly startedAtMs: TimestampMs;
|
|
106
|
+
readonly parentInvocationId?: string;
|
|
107
|
+
readonly durationMs: number;
|
|
108
|
+
}
|
|
109
|
+
interface InvocationResult<Value, Metadata extends InvocationMetadata> {
|
|
110
|
+
readonly value: Value;
|
|
111
|
+
readonly invocation: Readonly<Metadata>;
|
|
112
|
+
}
|
|
113
|
+
type ModelResult<Value> = InvocationResult<Value, ModelCallRecord>;
|
|
114
|
+
type ToolResult<Value> = InvocationResult<Value, ToolCallRecord>;
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Both model and tool invoke use this base structure. ModelCallRecord and ToolCallRecord keep their own known metadata and both extend InvocationMetadata; model usage exists only when the provider actually supplies it and is not fabricated as 0.
|
|
118
|
+
|
|
119
|
+
Plain text corresponds to ModelResult<string>, and structured protocols correspond to ModelResult<ParsedValue>: the outer layer is unchanged, only the business type of value differs. Failure always throws or rejects the Promise and must not be mixed with `{ success: false }` as a second error channel. The model raw stream, previews, and diagnostics use their own explicit interfaces and do not become alternative return modes of invoke.
|
|
120
|
+
|
|
121
|
+
This rule governs the boundaries of similar invocations. is/has, parse, read, and other distinct responsibilities still return per their explicit contracts; not every pure function is wrapped just to unify invoke.
|
|
122
|
+
|
|
123
|
+
## Errors and observation
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
interface AgentErrorInfo {
|
|
127
|
+
readonly code: string;
|
|
128
|
+
readonly codes: readonly string[];
|
|
129
|
+
readonly issues: readonly Issue[];
|
|
130
|
+
readonly stageId?: string;
|
|
131
|
+
readonly isRetryable?: boolean;
|
|
132
|
+
readonly cause: unknown;
|
|
133
|
+
}
|
|
134
|
+
type Observer<Event> = (event: Readonly<Event>) => void | Promise<void>;
|
|
135
|
+
type AsyncSink<Event> = (event: Readonly<Event>) => Promise<void>; // 需要异步交付的端口
|
|
136
|
+
// 接受同步完成的 sink 类型可返回 void;执行器仍按序 await。
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`getErrorInfo(error)` normalizes the error view and preserves the identity of App exceptions or cancellation reasons together with structured location. The SDK's ValidationError and the error view both use issues; budget resource, stage, and other details are not expressed by attaching another competing code to the same exception. The normalizer can also read the errors array of external exceptions; this is input format adaptation and does not make the SDK output two fields at once.
|
|
140
|
+
|
|
141
|
+
Observer is out of band: it is not awaited, its exceptions are isolated, and it does not change authorization, budget, or retries. AsyncSink is delivery: it awaits order and backpressure, propagates failures, and is subject to cancellation and timeouts. A critical handler is not called observe.
|
|
142
|
+
|
|
143
|
+
## Registration collections
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
interface ReadonlyCatalog<Definition extends { readonly id: string }> {
|
|
147
|
+
readonly definitions: readonly Readonly<Definition>[];
|
|
148
|
+
ids(): readonly string[];
|
|
149
|
+
values(): IterableIterator<Readonly<Definition>>;
|
|
150
|
+
entries(): IterableIterator<readonly [string, Readonly<Definition>]>;
|
|
151
|
+
has(id: string): boolean;
|
|
152
|
+
get(id: string): Readonly<Definition> | undefined;
|
|
153
|
+
require(id: string): Readonly<Definition>;
|
|
154
|
+
}
|
|
155
|
+
interface Registry<Definition extends { readonly id: string }>
|
|
156
|
+
extends ReadonlyCatalog<Definition> {
|
|
157
|
+
register(definition: Definition): this;
|
|
158
|
+
registerAll(definitions: Iterable<Definition>): this;
|
|
159
|
+
seal(): this;
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
registerAll does not implicitly seal; a factory that creates an already-compiled catalog returns a read-only catalog directly. If byId must be exposed, declare it explicitly as a Record and do not name it definitions or entries. A pure algorithm does not add registration entities just to implement this interface.
|
|
164
|
+
|
|
165
|
+
## Ownership, missing values, and generics
|
|
166
|
+
|
|
167
|
+
- Pure definition data (JSON schema, ID arrays, policy parameters) is copied and frozen; objects with methods are not structuredClone'd and not spread in a way that drops their prototype. Contract methods are read one by one, and the original methods and their original receivers are fixed; App instances are not frozen. Binding a receiver does not snapshot the instance's internal state, and handlers must still honor static and request lifecycle constraints.
|
|
168
|
+
- A registrar query returns the real stored registration projection: canonical methods are bound to their original receivers, and App-attached data and prototype getter metadata are read at registration; unregistered instance helpers are not promised. The canonical lists for intent, scope, and lifecycle are read-only snapshots, and describe returns an independent list copy; custom validation and policy callbacks still receive the original implementation so they can call its helpers.
|
|
169
|
+
- Readonly only constrains TypeScript writes; Map/Set and nested business objects are not automatically immutable through Object.freeze. A catalog does not expose its internal Map; business values are not promised a uniform deep copy, and each Kit defines the copy scope of its snapshot and persistence interfaces.
|
|
170
|
+
- Returned snapshots or checkpoint results must state whether they are copies or views; public events do not provide writable live references.
|
|
171
|
+
- Flow control uses explicit discrimination to distinguish continuation from a completion value; a legitimate generic final value may be undefined. Ordinary read/render/search returns the business value directly.
|
|
172
|
+
- Generic roles are ordered Input, Output, Context; roles that are absent are omitted. Complex local flows use named role types.
|
|
173
|
+
- After a static ID selection, types narrow with the actual collection; dynamic IDs keep runtime validation and the unknown boundary, and callers must not pass a self-reported result generic off as validation.
|
|
174
|
+
|
|
175
|
+
For complete exports, exact overloads, and declaration sources, see [Public symbol reference](reference.md). Signatures in examples are for reading only; omitted fields cannot be used to infer arbitrary extension options.
|
|
176
|
+
|
|
177
|
+
## Type entry points and base maps
|
|
178
|
+
|
|
179
|
+
`import type { ... } from '@jangts/agent-core/types'` covers shared contracts and the public types of each Kit and is empty at runtime; the root and owning modules also continue to provide type imports. `/structs` keeps historical types and existing constants and is not the preferred entry point for runtime values in new code.
|
|
180
|
+
|
|
181
|
+
`ProtocolRegistry<T>` is the only keyed Map that allows arbitrary protocol payloads and provides ids/require without requiring T to have an id, so it is not disguised as an entity Catalog; real entity catalogs such as CommandCatalog implement the shared collection surface. Ordered algorithm configurations such as Normalization.steps are not incrementally registerable Registries. Intent/Scope registerAll only registers, and sealing uses the subsequent seal(); create is the complete-list entry that combines both.
|