@adia-ai/agent 0.8.26
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 +7 -0
- package/README.md +172 -0
- package/agent.d.ts +53 -0
- package/agent.d.ts.map +1 -0
- package/agent.js +99 -0
- package/agent.js.map +1 -0
- package/events.d.ts +92 -0
- package/events.d.ts.map +1 -0
- package/events.js +112 -0
- package/events.js.map +1 -0
- package/frame.d.ts +60 -0
- package/frame.d.ts.map +1 -0
- package/frame.js +43 -0
- package/frame.js.map +1 -0
- package/index.d.ts +28 -0
- package/index.d.ts.map +1 -0
- package/index.js +18 -0
- package/index.js.map +1 -0
- package/integrations.d.ts +105 -0
- package/integrations.d.ts.map +1 -0
- package/integrations.js +167 -0
- package/integrations.js.map +1 -0
- package/loop.d.ts +44 -0
- package/loop.d.ts.map +1 -0
- package/loop.js +120 -0
- package/loop.js.map +1 -0
- package/package.json +41 -0
- package/prompt.d.ts +36 -0
- package/prompt.d.ts.map +1 -0
- package/prompt.js +38 -0
- package/prompt.js.map +1 -0
- package/resource.d.ts +27 -0
- package/resource.d.ts.map +1 -0
- package/resource.js +46 -0
- package/resource.js.map +1 -0
- package/session.d.ts +30 -0
- package/session.d.ts.map +1 -0
- package/session.js +30 -0
- package/session.js.map +1 -0
- package/stub.d.ts +22 -0
- package/stub.d.ts.map +1 -0
- package/stub.js +52 -0
- package/stub.js.map +1 -0
- package/tools.d.ts +43 -0
- package/tools.d.ts.map +1 -0
- package/tools.js +103 -0
- package/tools.js.map +1 -0
- package/workflow.d.ts +44 -0
- package/workflow.d.ts.map +1 -0
- package/workflow.js +36 -0
- package/workflow.js.map +1 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.8.26] — 2026-08-05
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **L1 hardening per CHAT-HARNESS.md laws 1–3 + 6 (WCH-1, gh#596).** `AgentEvent` gains two additive kinds — `progress` (closed, code-owned `ProgressStage` enum: `PROGRESS_STAGES` + `isProgressStage()` guard; the loop emits `sent`/`retry`/`tool`/`done` at its genuine natural points) and `surface` (one generative-UI wire line, envelope-keyed) — both render-only in `reduce()`. A byte-identity baseline gate (`baseline/prompt-equivalence.baseline.json` + `baseline.test.js`, regenerated via `scripts/build/regen-agent-baseline.mjs`) pins that every optional `AgentConfig` axis, when absent, leaves no trace on the derived `ChatOpts`. `frameClientMessage()` + `shouldRunTurn()` (`src/frame.ts`) map a surface action / function result / validation rejection to a distinct, deterministic natural-language user turn — silent-apply kinds (`dataModelUpdate`, `ack`) gate out before framing and throw if framed directly. `agent.sendClientMessage(session, msg)` wires gate → frame → loop.
|
|
7
|
+
- **Initial release — the composable chat-agent harness (gh#579).** `createAgent()` assembles an agent from four declared parts on top of `@adia-ai/llm`: cache-aware prompt layers (`promptLayer`), a JSON-schema'd tool registry with local-fn and endpoint executors plus the bounded execute-and-feed-back loop (`defineTool`, `maxToolRounds`, `onToolCall` gate), declarative workflow cascades (`defineWorkflow` — `until` accepts / terminal steps / `when` skips), and resources in `attach` (application-controlled prompt layer) or `tool` (model-controlled `read_<name>`) mode. Ships the serializable `Session` + the one shared event reducer (`reduce`), and `scriptClient` for deterministic keyless tests.
|
package/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# `@adia-ai/agent`
|
|
2
|
+
|
|
3
|
+
Composable chat-agent harness. Assemble an agent from four declared parts —
|
|
4
|
+
**prompt layers**, **tools**, **workflows**, **resources** — on top of
|
|
5
|
+
[`@adia-ai/llm`](../llm/README.md). The package owns the tool-call loop, the
|
|
6
|
+
serializable `Session`, and the one shared event reducer; it renders nothing
|
|
7
|
+
(pair it with `web-modules/chat` or any UI).
|
|
8
|
+
|
|
9
|
+
> **Experimental** until the A2UI pipeline migration validates the contracts
|
|
10
|
+
> (tracked in [gh#579](https://github.com/adiahealth/gen-ui-kit/issues/579)).
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @adia-ai/agent
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
import {
|
|
22
|
+
createAgent, promptLayer, defineTool, defineWorkflow,
|
|
23
|
+
defineResource, reduce,
|
|
24
|
+
} from '@adia-ai/agent';
|
|
25
|
+
|
|
26
|
+
const agent = createAgent({
|
|
27
|
+
llm: { model: 'claude-sonnet-4-6', proxyUrl: '/api/chat' },
|
|
28
|
+
prompt: [
|
|
29
|
+
promptLayer('identity', 'You are the AdiaUI copilot.', { cache: true }),
|
|
30
|
+
promptLayer('context', () => new Date().toISOString()), // per-turn, never cached
|
|
31
|
+
],
|
|
32
|
+
tools: [
|
|
33
|
+
defineTool({
|
|
34
|
+
name: 'search_patients',
|
|
35
|
+
description: 'Find a patient by name or MRN',
|
|
36
|
+
inputSchema: { type: 'object', properties: { q: { type: 'string' } }, required: ['q'] },
|
|
37
|
+
execute: async ({ q }) => lookup(q), // or endpoint: { url, method }
|
|
38
|
+
}),
|
|
39
|
+
],
|
|
40
|
+
workflows: [
|
|
41
|
+
defineWorkflow('generate', [
|
|
42
|
+
{ name: 'zettel', run: zettel, until: r => r.strategy === 'composition-match' },
|
|
43
|
+
{ name: 'free-form', run: freeForm, until: r => r.ok },
|
|
44
|
+
{ name: 'monolithic', run: monolithic }, // terminal — always accepted
|
|
45
|
+
]),
|
|
46
|
+
],
|
|
47
|
+
resources: [
|
|
48
|
+
defineResource({ name: 'visit-summary', mode: 'attach', get: fetchSummary }), // app-controlled
|
|
49
|
+
defineResource({ name: 'care-plan', mode: 'tool', get: readPlan }), // model-controlled
|
|
50
|
+
],
|
|
51
|
+
maxToolRounds: 8,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
let session = agent.createSession(); // plain JSON-safe object — you own persistence
|
|
55
|
+
for await (const event of agent.send(session, 'weather in Oslo?')) {
|
|
56
|
+
session = reduce(session, event); // the one reducer — the single session writer
|
|
57
|
+
render(event); // text | thinking | tool_use | tool_result | done | error
|
|
58
|
+
}
|
|
59
|
+
await agent.run(session, 'generate', { intent });
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## The four parts
|
|
63
|
+
|
|
64
|
+
| Part | Declared with | What the harness does |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| Prompt | `promptLayer(name, text, {cache})` | Static layers first, `cache_control` on the last cached one (Anthropic block array; joined string elsewhere) |
|
|
67
|
+
| Tools | `defineTool({name, inputSchema, execute\|endpoint})` | JSON-schema check at the call boundary, then the execute-and-feed-back loop (bounded by `maxToolRounds`; gate calls via `onToolCall`) |
|
|
68
|
+
| Workflows | `defineWorkflow(name, steps)` | Sequential cascade — `until` accepts, absent `until` is terminal, `when` skips |
|
|
69
|
+
| Resources | `defineResource({name, mode, get})` | `attach` → dynamic prompt layer each turn; `tool` → a read-only `read_<name>` tool |
|
|
70
|
+
|
|
71
|
+
## Events
|
|
72
|
+
|
|
73
|
+
`agent.send()` yields the `AgentEvent` union; `reduce(session, event)` folds it.
|
|
74
|
+
Every failure inside a tool call becomes an `isError` `tool_result` the model
|
|
75
|
+
can react to — the loop throws for nothing tool-shaped.
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
{ type:'message', role:'user', text }
|
|
79
|
+
{ type:'text', text, snapshot } · { type:'thinking', text }
|
|
80
|
+
{ type:'tool_use', id, name, input } · { type:'tool_result', id, name, output, isError? }
|
|
81
|
+
{ type:'step', workflow, step, data? }
|
|
82
|
+
{ type:'progress', stage } · { type:'surface', surfaceId, line }
|
|
83
|
+
{ type:'done', text, usage, stopReason } · { type:'error', error }
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`stopReason` passes through raw from the provider; the loop adds exactly one
|
|
87
|
+
synthetic value, `max_tool_rounds`, when the bound is hit.
|
|
88
|
+
|
|
89
|
+
`thinking` / `step` / `error` / `progress` / `surface` are render-only —
|
|
90
|
+
`reduce()` folds them into no session change.
|
|
91
|
+
|
|
92
|
+
### Progress events (CHAT-HARNESS law 3)
|
|
93
|
+
|
|
94
|
+
`progress` carries a CLOSED, code-owned `stage`: one of `PROGRESS_STAGES`
|
|
95
|
+
(`'sent' | 'started' | 'reasoning' | 'content' | 'validating' | 'retry' |
|
|
96
|
+
'tool' | 'done'`), exported alongside the `isProgressStage()` guard so any
|
|
97
|
+
reader — a deserialized wire message, a stored transcript — can drop an
|
|
98
|
+
out-of-vocabulary value instead of rendering it. **Never render `stage` as
|
|
99
|
+
label text directly**; a UI's label table is code-owned and keyed on these
|
|
100
|
+
strings, never on model output (the honesty guard).
|
|
101
|
+
|
|
102
|
+
The loop today emits only the stages it genuinely reaches — `sent` before
|
|
103
|
+
every provider call, `retry` before a call that repeats a round after tool
|
|
104
|
+
feedback, `tool` before each tool executes, `done` right before the
|
|
105
|
+
terminal `done` event. `started` / `reasoning` / `content` / `validating`
|
|
106
|
+
are declared in the vocabulary for future producers (the L1 loop doesn't
|
|
107
|
+
independently observe those signals today, so it doesn't invent an
|
|
108
|
+
emission point for them).
|
|
109
|
+
|
|
110
|
+
### Surface events
|
|
111
|
+
|
|
112
|
+
`{ type:'surface', surfaceId, line }` carries one generative-UI wire line
|
|
113
|
+
for an envelope-keyed surface. This package only carries the event kind on
|
|
114
|
+
the union — turning it into a rendered surface is L3's job
|
|
115
|
+
(`web-modules/chat`, WCH-4), never this package's.
|
|
116
|
+
|
|
117
|
+
### Framed client-message turns (CHAT-HARNESS law 6)
|
|
118
|
+
|
|
119
|
+
A surface action, a function/tool result, or a validation rejection
|
|
120
|
+
re-enters the agent as a *turn* — never a raw data blob — through one
|
|
121
|
+
framing function:
|
|
122
|
+
|
|
123
|
+
```js
|
|
124
|
+
import { frameClientMessage, shouldRunTurn } from '@adia-ai/agent';
|
|
125
|
+
|
|
126
|
+
const msg = { kind: 'surfaceAction', surfaceId: 'sf_1', action: 'submit', context: { formId: 'f1' } };
|
|
127
|
+
if (shouldRunTurn(msg)) {
|
|
128
|
+
for await (const event of agent.sendClientMessage(session, msg)) { /* … */ }
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`ClientMessage` arms and their framed wording:
|
|
133
|
+
|
|
134
|
+
| Kind | Framed turn |
|
|
135
|
+
|---|---|
|
|
136
|
+
| `surfaceAction` | `The user triggered the ${action} action on surface ${surfaceId} with context ${JSON(context)}.` |
|
|
137
|
+
| `functionResult` | `The function ${call} returned: ${JSON(value)}.` |
|
|
138
|
+
| `validationRejection` | `The previous surface was rejected (${code}): ${message}. Emit a corrected surface.` |
|
|
139
|
+
| `dataModelUpdate`, `ack` | silent-apply — `shouldRunTurn` returns `false`; `frameClientMessage` **throws** if called on one directly |
|
|
140
|
+
|
|
141
|
+
`agent.sendClientMessage(session, msg, opts?)` is the wired entry: it gates
|
|
142
|
+
with `shouldRunTurn`, frames with `frameClientMessage`, then runs the same
|
|
143
|
+
loop as `agent.send()`. A silent-apply message yields nothing and never
|
|
144
|
+
touches the provider — the caller already applied it directly (e.g. to a
|
|
145
|
+
surface's data-model store).
|
|
146
|
+
|
|
147
|
+
## Byte-identity baseline (CHAT-HARNESS law 2)
|
|
148
|
+
|
|
149
|
+
Every axis on `AgentConfig` is optional, and absence must produce a
|
|
150
|
+
byte-identical request to before the axis existed. `baseline/prompt-
|
|
151
|
+
equivalence.baseline.json` pins the exact `ChatOpts` a minimal config (one
|
|
152
|
+
static prompt layer, nothing else) produces; `baseline.test.js` re-derives
|
|
153
|
+
it every run and diffs byte-for-byte, including asserting no optional-axis
|
|
154
|
+
key (`tools`, `cache`, `signal`, …) appears at all — not even as an empty
|
|
155
|
+
array.
|
|
156
|
+
|
|
157
|
+
Changing a default that legitimately changes this output breaks the test
|
|
158
|
+
until the baseline is deliberately regenerated:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
npm run build -w @adia-ai/agent
|
|
162
|
+
node scripts/build/regen-agent-baseline.mjs
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Never hand-edit the JSON file — regenerate it, then diff-review the change
|
|
166
|
+
like any other pinned contract.
|
|
167
|
+
|
|
168
|
+
## Testing
|
|
169
|
+
|
|
170
|
+
`scriptClient(turns)` is a deterministic `LLMClient`: script `[{text, toolUse}]`
|
|
171
|
+
turns and drive the whole loop keylessly. It also records every `ChatOpts` it
|
|
172
|
+
was called with (`client.calls`) so tests can assert what reached the wire.
|
package/agent.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createAgent — assemble a chat agent from the four declared parts.
|
|
3
|
+
*
|
|
4
|
+
* The agent owns wiring, not state: send() streams AgentEvents through the
|
|
5
|
+
* tool loop, run() drives a named workflow. The caller holds the Session
|
|
6
|
+
* and folds events with reduce() — the agent never mutates it.
|
|
7
|
+
*
|
|
8
|
+
* System-prompt shape is provider-decided here (the one place that knows
|
|
9
|
+
* the provider): Anthropic gets the block array with cache_control markers,
|
|
10
|
+
* everyone else gets the joined string.
|
|
11
|
+
*/
|
|
12
|
+
import type { ChatOpts, LLMClient, ToolUse } from '@adia-ai/llm';
|
|
13
|
+
import { type PromptLayer } from './prompt.js';
|
|
14
|
+
import { type ResourceDef } from './resource.js';
|
|
15
|
+
import { type Session } from './session.js';
|
|
16
|
+
import type { AgentEvent } from './events.js';
|
|
17
|
+
import { type ClientMessage } from './frame.js';
|
|
18
|
+
import type { ToolContext, ToolDef } from './tools.js';
|
|
19
|
+
import { type Workflow, type WorkflowResult } from './workflow.js';
|
|
20
|
+
export interface AgentConfig {
|
|
21
|
+
/** Client defaults (model, proxyUrl, apiKey…) handed to createClient(),
|
|
22
|
+
* or a ready-made client under `client` (tests: scriptClient; browser
|
|
23
|
+
* demos passing a client built from /packages/llm/index.js directly). */
|
|
24
|
+
llm: Partial<ChatOpts> | {
|
|
25
|
+
client: LLMClient;
|
|
26
|
+
model?: string;
|
|
27
|
+
provider?: string;
|
|
28
|
+
};
|
|
29
|
+
prompt?: PromptLayer[];
|
|
30
|
+
tools?: ToolDef[];
|
|
31
|
+
workflows?: Array<Workflow<never, never>> | Workflow[];
|
|
32
|
+
resources?: ResourceDef[];
|
|
33
|
+
maxToolRounds?: number;
|
|
34
|
+
/** Gate every tool call; false denies it (isError feedback, loop continues). */
|
|
35
|
+
onToolCall?: (call: ToolUse, ctx: ToolContext) => boolean | Promise<boolean>;
|
|
36
|
+
}
|
|
37
|
+
export interface SendOpts {
|
|
38
|
+
signal?: AbortSignal;
|
|
39
|
+
}
|
|
40
|
+
export interface Agent {
|
|
41
|
+
createSession(id?: string): Session;
|
|
42
|
+
send(session: Session, text: string, opts?: SendOpts): AsyncGenerator<AgentEvent>;
|
|
43
|
+
/** Law 6 — gates the message with `shouldRunTurn`, frames it with
|
|
44
|
+
* `frameClientMessage`, then runs it through the same loop as `send`.
|
|
45
|
+
* A silent-apply message (shouldRunTurn ⇒ false) yields nothing and
|
|
46
|
+
* never touches the loop — the caller already applied it directly. */
|
|
47
|
+
sendClientMessage(session: Session, msg: ClientMessage, opts?: SendOpts): AsyncGenerator<AgentEvent>;
|
|
48
|
+
run<TResult = unknown>(session: Session, workflowName: string, input?: unknown, onEvent?: (event: AgentEvent) => void): Promise<WorkflowResult<TResult>>;
|
|
49
|
+
/** The assembled tool set (declared + tool-mode resources) — inspectable. */
|
|
50
|
+
tools: ToolDef[];
|
|
51
|
+
}
|
|
52
|
+
export declare function createAgent(config: AgentConfig): Agent;
|
|
53
|
+
//# sourceMappingURL=agent.d.ts.map
|
package/agent.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["src/agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAYjE,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAA+B,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,EAAiB,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAqC,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAEnF,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EAAe,KAAK,QAAQ,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAIhF,MAAM,WAAW,WAAW;IAC1B;;8EAE0E;IAC1E,GAAG,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG;QAAE,MAAM,EAAE,SAAS,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClF,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAClB,SAAS,CAAC,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,GAAG,QAAQ,EAAE,CAAC;IACvD,SAAS,CAAC,EAAE,WAAW,EAAE,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gFAAgF;IAChF,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,WAAW,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9E;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,KAAK;IACpB,aAAa,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACpC,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;IAClF;;;2EAGuE;IACvE,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;IACrG,GAAG,CAAC,OAAO,GAAG,OAAO,EACnB,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,MAAM,EACpB,KAAK,CAAC,EAAE,OAAO,EACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GACpC,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACpC,6EAA6E;IAC7E,KAAK,EAAE,OAAO,EAAE,CAAC;CAClB;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,KAAK,CAgFtD"}
|
package/agent.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createAgent — assemble a chat agent from the four declared parts.
|
|
3
|
+
*
|
|
4
|
+
* The agent owns wiring, not state: send() streams AgentEvents through the
|
|
5
|
+
* tool loop, run() drives a named workflow. The caller holds the Session
|
|
6
|
+
* and folds events with reduce() — the agent never mutates it.
|
|
7
|
+
*
|
|
8
|
+
* System-prompt shape is provider-decided here (the one place that knows
|
|
9
|
+
* the provider): Anthropic gets the block array with cache_control markers,
|
|
10
|
+
* everyone else gets the joined string.
|
|
11
|
+
*/
|
|
12
|
+
let _llm = null;
|
|
13
|
+
function llmModule() {
|
|
14
|
+
if (!_llm)
|
|
15
|
+
_llm = import('@adia-ai/llm').catch(() => null);
|
|
16
|
+
return _llm;
|
|
17
|
+
}
|
|
18
|
+
import { composeSystem } from './prompt.js';
|
|
19
|
+
import { attachLayers, resourceTools } from './resource.js';
|
|
20
|
+
import { createSession } from './session.js';
|
|
21
|
+
import { frameClientMessage, shouldRunTurn } from './frame.js';
|
|
22
|
+
import { runLoop } from './loop.js';
|
|
23
|
+
import { runWorkflow } from './workflow.js';
|
|
24
|
+
const DEFAULT_MAX_TOOL_ROUNDS = 8;
|
|
25
|
+
export function createAgent(config) {
|
|
26
|
+
const hasClient = 'client' in config.llm && !!config.llm.client;
|
|
27
|
+
const model = config.llm.model;
|
|
28
|
+
const explicitProvider = config.llm.provider;
|
|
29
|
+
let _resolved = null;
|
|
30
|
+
async function resolveClient() {
|
|
31
|
+
if (_resolved)
|
|
32
|
+
return _resolved;
|
|
33
|
+
const mod = await llmModule();
|
|
34
|
+
const provider = explicitProvider ?? (model ? mod?.detectProviderFromModel(model) ?? undefined : undefined);
|
|
35
|
+
if (hasClient) {
|
|
36
|
+
_resolved = { client: config.llm.client, provider };
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
if (!mod)
|
|
40
|
+
throw new Error('@adia-ai/llm is not available — build packages/llm, or pass llm.client');
|
|
41
|
+
_resolved = { client: mod.createClient(config.llm), provider };
|
|
42
|
+
}
|
|
43
|
+
return _resolved;
|
|
44
|
+
}
|
|
45
|
+
const layers = config.prompt ?? [];
|
|
46
|
+
const resources = config.resources ?? [];
|
|
47
|
+
const tools = [...(config.tools ?? []), ...resourceTools(resources)];
|
|
48
|
+
const workflows = new Map((config.workflows ?? []).map(w => [w.name, w]));
|
|
49
|
+
const maxToolRounds = config.maxToolRounds ?? DEFAULT_MAX_TOOL_ROUNDS;
|
|
50
|
+
async function chatOverrides(provider) {
|
|
51
|
+
const allLayers = [...layers, ...(await attachLayers(resources))];
|
|
52
|
+
if (!allLayers.length)
|
|
53
|
+
return {};
|
|
54
|
+
const composed = composeSystem(allLayers);
|
|
55
|
+
// Anthropic takes the block array (cache markers ride the blocks, so
|
|
56
|
+
// ChatOpts.cache stays off — the adapter's cache flag would re-wrap).
|
|
57
|
+
if (provider === 'anthropic')
|
|
58
|
+
return { system: composed.blocks, cache: false };
|
|
59
|
+
return { system: composed.text };
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
tools,
|
|
63
|
+
createSession(id) {
|
|
64
|
+
return createSession(id);
|
|
65
|
+
},
|
|
66
|
+
async *send(session, text, opts = {}) {
|
|
67
|
+
yield { type: 'message', role: 'user', text };
|
|
68
|
+
const { client, provider } = await resolveClient();
|
|
69
|
+
const messages = [...session.messages, { role: 'user', content: text }];
|
|
70
|
+
const ctx = { sessionId: session.id, ...(opts.signal ? { signal: opts.signal } : {}) };
|
|
71
|
+
yield* runLoop(messages, {
|
|
72
|
+
client,
|
|
73
|
+
chat: await chatOverrides(provider),
|
|
74
|
+
tools,
|
|
75
|
+
maxToolRounds,
|
|
76
|
+
...(config.onToolCall ? { onToolCall: config.onToolCall } : {}),
|
|
77
|
+
ctx,
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
async *sendClientMessage(session, msg, opts = {}) {
|
|
81
|
+
if (!shouldRunTurn(msg))
|
|
82
|
+
return;
|
|
83
|
+
const text = frameClientMessage(msg);
|
|
84
|
+
yield* this.send(session, text, opts);
|
|
85
|
+
},
|
|
86
|
+
async run(session, workflowName, input, onEvent) {
|
|
87
|
+
const workflow = workflows.get(workflowName);
|
|
88
|
+
if (!workflow) {
|
|
89
|
+
throw new Error(`Unknown workflow "${workflowName}". Declared: ${[...workflows.keys()].join(', ') || '(none)'}`);
|
|
90
|
+
}
|
|
91
|
+
return runWorkflow(workflow, {
|
|
92
|
+
input,
|
|
93
|
+
sessionId: session.id,
|
|
94
|
+
...(onEvent ? { emit: (e) => onEvent(e) } : {}),
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=agent.js.map
|
package/agent.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["src/agent.ts"],"names":[],"mappings":"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"}
|
package/events.d.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentEvent — the one stream contract, and reduce() — the one reducer.
|
|
3
|
+
*
|
|
4
|
+
* Every surface consuming an agent renders from this union and folds
|
|
5
|
+
* session state with this reducer; hand-written per-surface switches over
|
|
6
|
+
* ad-hoc event shapes are the defect class this module exists to kill.
|
|
7
|
+
*
|
|
8
|
+
* reduce() is pure: (session, event) → new session. The loop emits events
|
|
9
|
+
* but never touches the caller's Session — reduce is the single writer.
|
|
10
|
+
*/
|
|
11
|
+
import type { AdapterUsage } from '@adia-ai/llm';
|
|
12
|
+
import type { Msg, Session } from './session.js';
|
|
13
|
+
/** CHAT-HARNESS law 3 — the closed, code-owned progress vocabulary. Every
|
|
14
|
+
* reader that renders labels does so from its OWN table keyed on these
|
|
15
|
+
* strings — never from model text — and drops anything not in this array
|
|
16
|
+
* (see `isProgressStage`). Extending this list is an amendment to the
|
|
17
|
+
* ruled contract, not a routine addition. */
|
|
18
|
+
export declare const PROGRESS_STAGES: readonly ["sent", "started", "reasoning", "content", "validating", "retry", "tool", "done"];
|
|
19
|
+
export type ProgressStage = (typeof PROGRESS_STAGES)[number];
|
|
20
|
+
/** Guard for any reader (a deserialized wire message, a stored transcript,
|
|
21
|
+
* a future stage a newer producer emits) — out-of-vocabulary stages must
|
|
22
|
+
* be droppable here rather than rendered blind. */
|
|
23
|
+
export declare function isProgressStage(value: unknown): value is ProgressStage;
|
|
24
|
+
export type AgentEvent = {
|
|
25
|
+
type: 'message';
|
|
26
|
+
role: 'user';
|
|
27
|
+
text: string;
|
|
28
|
+
} | {
|
|
29
|
+
type: 'text';
|
|
30
|
+
text: string;
|
|
31
|
+
snapshot: string;
|
|
32
|
+
} | {
|
|
33
|
+
type: 'thinking';
|
|
34
|
+
text: string;
|
|
35
|
+
} | {
|
|
36
|
+
type: 'tool_use';
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
input: Record<string, unknown>;
|
|
40
|
+
} | {
|
|
41
|
+
type: 'tool_result';
|
|
42
|
+
id: string;
|
|
43
|
+
name: string;
|
|
44
|
+
output: string;
|
|
45
|
+
isError?: boolean;
|
|
46
|
+
} | {
|
|
47
|
+
type: 'step';
|
|
48
|
+
workflow: string;
|
|
49
|
+
step: string;
|
|
50
|
+
data?: unknown;
|
|
51
|
+
} | {
|
|
52
|
+
type: 'done';
|
|
53
|
+
text: string;
|
|
54
|
+
usage: AdapterUsage;
|
|
55
|
+
stopReason: string;
|
|
56
|
+
} | {
|
|
57
|
+
type: 'error';
|
|
58
|
+
error: Error;
|
|
59
|
+
}
|
|
60
|
+
/** Law 3 — a closed-vocabulary progress marker; render-only, no session
|
|
61
|
+
* effect (see reduce()). */
|
|
62
|
+
| {
|
|
63
|
+
type: 'progress';
|
|
64
|
+
stage: ProgressStage;
|
|
65
|
+
}
|
|
66
|
+
/** A generative-UI line for one envelope-keyed surface; render-only, no
|
|
67
|
+
* session effect (see reduce()). Surface HOSTING (L3, WCH-4) owns
|
|
68
|
+
* turning this into a rendered surface — this package only carries the
|
|
69
|
+
* event kind on the union. */
|
|
70
|
+
| {
|
|
71
|
+
type: 'surface';
|
|
72
|
+
surfaceId: string;
|
|
73
|
+
line: string;
|
|
74
|
+
};
|
|
75
|
+
/** Fold one event into a Session. Message-building rules:
|
|
76
|
+
* - `message` → append the user message.
|
|
77
|
+
* - `text` → update the draft's text snapshot.
|
|
78
|
+
* - `tool_use` → flush draft text into parts, append the tool_use part.
|
|
79
|
+
* - `tool_result` → close the draft as an assistant message (first result
|
|
80
|
+
* after tool_use parts), then append to the tool message
|
|
81
|
+
* (consecutive results merge into ONE tool msg — the
|
|
82
|
+
* Anthropic wire requires alternating roles).
|
|
83
|
+
* - `done` → close any remaining draft as the final assistant msg.
|
|
84
|
+
* - `thinking`/`step`/`error`/`progress`/`surface` → no session effect
|
|
85
|
+
* (render-only events — `progress`/`surface` included by the same rule:
|
|
86
|
+
* they narrate the turn in flight, they don't alter the transcript).
|
|
87
|
+
*/
|
|
88
|
+
export declare function reduce(session: Session, event: AgentEvent): Session;
|
|
89
|
+
/** Convenience for the loop and tests: fold a whole event array. */
|
|
90
|
+
export declare function reduceAll(session: Session, events: AgentEvent[]): Session;
|
|
91
|
+
export type { Msg, Session };
|
|
92
|
+
//# sourceMappingURL=events.d.ts.map
|
package/events.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["src/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAQ,MAAM,cAAc,CAAC;AACvD,OAAO,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEjD;;;;8CAI8C;AAC9C,eAAO,MAAM,eAAe,6FAElB,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D;;oDAEoD;AACpD,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,aAAa,CAEtE;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAC9E;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACpF;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GAChE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,YAAY,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACvE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE;AACjC;6BAC6B;GAC3B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,aAAa,CAAA;CAAE;AAC5C;;;+BAG+B;GAC7B;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzD;;;;;;;;;;;;GAYG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAsEnE;AAED,oEAAoE;AACpE,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,OAAO,CAIzE;AAED,YAAY,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC"}
|
package/events.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentEvent — the one stream contract, and reduce() — the one reducer.
|
|
3
|
+
*
|
|
4
|
+
* Every surface consuming an agent renders from this union and folds
|
|
5
|
+
* session state with this reducer; hand-written per-surface switches over
|
|
6
|
+
* ad-hoc event shapes are the defect class this module exists to kill.
|
|
7
|
+
*
|
|
8
|
+
* reduce() is pure: (session, event) → new session. The loop emits events
|
|
9
|
+
* but never touches the caller's Session — reduce is the single writer.
|
|
10
|
+
*/
|
|
11
|
+
/** CHAT-HARNESS law 3 — the closed, code-owned progress vocabulary. Every
|
|
12
|
+
* reader that renders labels does so from its OWN table keyed on these
|
|
13
|
+
* strings — never from model text — and drops anything not in this array
|
|
14
|
+
* (see `isProgressStage`). Extending this list is an amendment to the
|
|
15
|
+
* ruled contract, not a routine addition. */
|
|
16
|
+
export const PROGRESS_STAGES = [
|
|
17
|
+
'sent', 'started', 'reasoning', 'content', 'validating', 'retry', 'tool', 'done',
|
|
18
|
+
];
|
|
19
|
+
/** Guard for any reader (a deserialized wire message, a stored transcript,
|
|
20
|
+
* a future stage a newer producer emits) — out-of-vocabulary stages must
|
|
21
|
+
* be droppable here rather than rendered blind. */
|
|
22
|
+
export function isProgressStage(value) {
|
|
23
|
+
return typeof value === 'string' && PROGRESS_STAGES.includes(value);
|
|
24
|
+
}
|
|
25
|
+
/** Fold one event into a Session. Message-building rules:
|
|
26
|
+
* - `message` → append the user message.
|
|
27
|
+
* - `text` → update the draft's text snapshot.
|
|
28
|
+
* - `tool_use` → flush draft text into parts, append the tool_use part.
|
|
29
|
+
* - `tool_result` → close the draft as an assistant message (first result
|
|
30
|
+
* after tool_use parts), then append to the tool message
|
|
31
|
+
* (consecutive results merge into ONE tool msg — the
|
|
32
|
+
* Anthropic wire requires alternating roles).
|
|
33
|
+
* - `done` → close any remaining draft as the final assistant msg.
|
|
34
|
+
* - `thinking`/`step`/`error`/`progress`/`surface` → no session effect
|
|
35
|
+
* (render-only events — `progress`/`surface` included by the same rule:
|
|
36
|
+
* they narrate the turn in flight, they don't alter the transcript).
|
|
37
|
+
*/
|
|
38
|
+
export function reduce(session, event) {
|
|
39
|
+
switch (event.type) {
|
|
40
|
+
case 'message':
|
|
41
|
+
return {
|
|
42
|
+
...session,
|
|
43
|
+
messages: [...session.messages, { role: 'user', content: event.text }],
|
|
44
|
+
};
|
|
45
|
+
case 'text': {
|
|
46
|
+
const draft = session.draft ?? { text: '', parts: [] };
|
|
47
|
+
return { ...session, draft: { ...draft, text: event.snapshot } };
|
|
48
|
+
}
|
|
49
|
+
case 'tool_use': {
|
|
50
|
+
const draft = session.draft ?? { text: '', parts: [] };
|
|
51
|
+
const parts = [...draft.parts];
|
|
52
|
+
if (draft.text && !parts.some(p => p.type === 'text')) {
|
|
53
|
+
parts.unshift({ type: 'text', text: draft.text });
|
|
54
|
+
}
|
|
55
|
+
parts.push({ type: 'tool_use', id: event.id, name: event.name, input: event.input });
|
|
56
|
+
return { ...session, draft: { text: draft.text, parts } };
|
|
57
|
+
}
|
|
58
|
+
case 'tool_result': {
|
|
59
|
+
const resultPart = {
|
|
60
|
+
type: 'tool_result',
|
|
61
|
+
toolUseId: event.id,
|
|
62
|
+
name: event.name,
|
|
63
|
+
content: event.output,
|
|
64
|
+
...(event.isError ? { isError: true } : {}),
|
|
65
|
+
};
|
|
66
|
+
const messages = [...session.messages];
|
|
67
|
+
const draft = session.draft;
|
|
68
|
+
if (draft?.parts.length) {
|
|
69
|
+
messages.push({ role: 'assistant', content: draft.parts });
|
|
70
|
+
}
|
|
71
|
+
const last = messages[messages.length - 1];
|
|
72
|
+
if (draft?.parts.length || last?.role !== 'tool' || typeof last.content === 'string') {
|
|
73
|
+
messages.push({ role: 'tool', content: [resultPart] });
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
messages[messages.length - 1] = { role: 'tool', content: [...last.content, resultPart] };
|
|
77
|
+
}
|
|
78
|
+
const next = { ...session, messages };
|
|
79
|
+
delete next.draft;
|
|
80
|
+
return next;
|
|
81
|
+
}
|
|
82
|
+
case 'done': {
|
|
83
|
+
const messages = [...session.messages];
|
|
84
|
+
const draft = session.draft;
|
|
85
|
+
if (draft?.parts.length) {
|
|
86
|
+
messages.push({ role: 'assistant', content: draft.parts });
|
|
87
|
+
}
|
|
88
|
+
else if (event.text || draft?.text) {
|
|
89
|
+
messages.push({ role: 'assistant', content: event.text || draft?.text || '' });
|
|
90
|
+
}
|
|
91
|
+
const next = { ...session, messages };
|
|
92
|
+
delete next.draft;
|
|
93
|
+
return next;
|
|
94
|
+
}
|
|
95
|
+
case 'thinking':
|
|
96
|
+
case 'step':
|
|
97
|
+
case 'error':
|
|
98
|
+
case 'progress':
|
|
99
|
+
case 'surface':
|
|
100
|
+
return session;
|
|
101
|
+
default:
|
|
102
|
+
return session;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/** Convenience for the loop and tests: fold a whole event array. */
|
|
106
|
+
export function reduceAll(session, events) {
|
|
107
|
+
let next = session;
|
|
108
|
+
for (const event of events)
|
|
109
|
+
next = reduce(next, event);
|
|
110
|
+
return next;
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=events.js.map
|
package/events.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["src/events.ts"],"names":[],"mappings":"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"}
|
package/frame.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* frameClientMessage / shouldRunTurn — law 6: framed client-message turns.
|
|
3
|
+
*
|
|
4
|
+
* The session is a plain serializable turn array (session.ts); every
|
|
5
|
+
* surface action, function/tool result, and validation rejection re-enters
|
|
6
|
+
* the loop as a *distinct natural-language user turn* through this ONE
|
|
7
|
+
* framing function — never scattered ad-hoc string-building at call sites.
|
|
8
|
+
*
|
|
9
|
+
* Silent-apply kinds (a data-model echo, an action-response ack the caller
|
|
10
|
+
* already consumed) never construct a turn at all: `shouldRunTurn` gates
|
|
11
|
+
* them out BEFORE framing is attempted, and `frameClientMessage` itself
|
|
12
|
+
* THROWS if handed one directly — a should-not-run message is
|
|
13
|
+
* unconstructable as a turn, not merely skipped. This mirrors the wire's
|
|
14
|
+
* own three-arm client vocabulary (action / error / functionResponse) one
|
|
15
|
+
* level up, in plain language the model reads as a user turn.
|
|
16
|
+
*/
|
|
17
|
+
export type ClientMessage =
|
|
18
|
+
/** A surface action fired (a button, a form submit) — carries the
|
|
19
|
+
* action's declared context, already resolved off the surface's store. */
|
|
20
|
+
{
|
|
21
|
+
kind: 'surfaceAction';
|
|
22
|
+
surfaceId: string;
|
|
23
|
+
action: string;
|
|
24
|
+
context?: unknown;
|
|
25
|
+
}
|
|
26
|
+
/** A model-callable function returned its result. */
|
|
27
|
+
| {
|
|
28
|
+
kind: 'functionResult';
|
|
29
|
+
call: string;
|
|
30
|
+
value: unknown;
|
|
31
|
+
}
|
|
32
|
+
/** A previously emitted surface failed validation — the harness asks for
|
|
33
|
+
* a corrected one. */
|
|
34
|
+
| {
|
|
35
|
+
kind: 'validationRejection';
|
|
36
|
+
code: string;
|
|
37
|
+
message: string;
|
|
38
|
+
}
|
|
39
|
+
/** Silent-apply: a data-model value changed and was already applied to
|
|
40
|
+
* the surface's store directly — no narration, no turn. */
|
|
41
|
+
| {
|
|
42
|
+
kind: 'dataModelUpdate';
|
|
43
|
+
surfaceId: string;
|
|
44
|
+
path?: string;
|
|
45
|
+
value: unknown;
|
|
46
|
+
}
|
|
47
|
+
/** Silent-apply: an action-response the caller already resolved off its
|
|
48
|
+
* own promise — nothing left to narrate. */
|
|
49
|
+
| {
|
|
50
|
+
kind: 'ack';
|
|
51
|
+
actionId: string;
|
|
52
|
+
};
|
|
53
|
+
/** Gate BEFORE framing: false means this message applies silently and must
|
|
54
|
+
* never reach `frameClientMessage`. */
|
|
55
|
+
export declare function shouldRunTurn(msg: ClientMessage): boolean;
|
|
56
|
+
/** Maps one client-message arm to its pinned natural-language user turn.
|
|
57
|
+
* Deterministic — same input, same string, always — so golden tests can
|
|
58
|
+
* pin the exact wording. Throws for any kind `shouldRunTurn` rejects. */
|
|
59
|
+
export declare function frameClientMessage(msg: ClientMessage): string;
|
|
60
|
+
//# sourceMappingURL=frame.d.ts.map
|
package/frame.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame.d.ts","sourceRoot":"","sources":["src/frame.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,MAAM,aAAa;AACvB;2EAC2E;AACzE;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE;AACjF,qDAAqD;GACnD;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE;AAC1D;uBACuB;GACrB;IAAE,IAAI,EAAE,qBAAqB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;AAChE;4DAC4D;GAC1D;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE;AAC/E;6CAC6C;GAC3C;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAItC;wCACwC;AACxC,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAEzD;AAED;;0EAE0E;AAC1E,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAsB7D"}
|