@ouispec/agent-worker 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +175 -0
- package/dist/approvals/client.d.ts +29 -0
- package/dist/approvals/client.d.ts.map +1 -0
- package/dist/approvals/client.js +50 -0
- package/dist/approvals/client.js.map +1 -0
- package/dist/approvals/continuation.d.ts +28 -0
- package/dist/approvals/continuation.d.ts.map +1 -0
- package/dist/approvals/continuation.js +43 -0
- package/dist/approvals/continuation.js.map +1 -0
- package/dist/approvals/preview.d.ts +15 -0
- package/dist/approvals/preview.d.ts.map +1 -0
- package/dist/approvals/preview.js +33 -0
- package/dist/approvals/preview.js.map +1 -0
- package/dist/approvals/requirement.d.ts +15 -0
- package/dist/approvals/requirement.d.ts.map +1 -0
- package/dist/approvals/requirement.js +26 -0
- package/dist/approvals/requirement.js.map +1 -0
- package/dist/authz/tool-policy.d.ts +63 -0
- package/dist/authz/tool-policy.d.ts.map +1 -0
- package/dist/authz/tool-policy.js +23 -0
- package/dist/authz/tool-policy.js.map +1 -0
- package/dist/container/server.d.ts +24 -0
- package/dist/container/server.d.ts.map +1 -0
- package/dist/container/server.js +119 -0
- package/dist/container/server.js.map +1 -0
- package/dist/emit/http-adapter.d.ts +15 -0
- package/dist/emit/http-adapter.d.ts.map +1 -0
- package/dist/emit/http-adapter.js +35 -0
- package/dist/emit/http-adapter.js.map +1 -0
- package/dist/emit/types.d.ts +9 -0
- package/dist/emit/types.d.ts.map +1 -0
- package/dist/emit/types.js +7 -0
- package/dist/emit/types.js.map +1 -0
- package/dist/events/http-waiter.d.ts +19 -0
- package/dist/events/http-waiter.d.ts.map +1 -0
- package/dist/events/http-waiter.js +20 -0
- package/dist/events/http-waiter.js.map +1 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +45 -0
- package/dist/index.js.map +1 -0
- package/dist/lambda/handler.d.ts +38 -0
- package/dist/lambda/handler.d.ts.map +1 -0
- package/dist/lambda/handler.js +26 -0
- package/dist/lambda/handler.js.map +1 -0
- package/dist/model.d.ts +33 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +15 -0
- package/dist/model.js.map +1 -0
- package/dist/openapi/document.d.ts +87 -0
- package/dist/openapi/document.d.ts.map +1 -0
- package/dist/openapi/document.js +117 -0
- package/dist/openapi/document.js.map +1 -0
- package/dist/openapi/executor.d.ts +51 -0
- package/dist/openapi/executor.d.ts.map +1 -0
- package/dist/openapi/executor.js +140 -0
- package/dist/openapi/executor.js.map +1 -0
- package/dist/openapi/index.d.ts +14 -0
- package/dist/openapi/index.d.ts.map +1 -0
- package/dist/openapi/index.js +9 -0
- package/dist/openapi/index.js.map +1 -0
- package/dist/openapi/loader.d.ts +81 -0
- package/dist/openapi/loader.d.ts.map +1 -0
- package/dist/openapi/loader.js +361 -0
- package/dist/openapi/loader.js.map +1 -0
- package/dist/openapi/x-agent.d.ts +34 -0
- package/dist/openapi/x-agent.d.ts.map +1 -0
- package/dist/openapi/x-agent.js +102 -0
- package/dist/openapi/x-agent.js.map +1 -0
- package/dist/orchestrator.d.ts +12 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +1019 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/prompt/builder.d.ts +29 -0
- package/dist/prompt/builder.d.ts.map +1 -0
- package/dist/prompt/builder.js +129 -0
- package/dist/prompt/builder.js.map +1 -0
- package/dist/prompt/index.d.ts +15 -0
- package/dist/prompt/index.d.ts.map +1 -0
- package/dist/prompt/index.js +13 -0
- package/dist/prompt/index.js.map +1 -0
- package/dist/prompt/rules/base.d.ts +6 -0
- package/dist/prompt/rules/base.d.ts.map +1 -0
- package/dist/prompt/rules/base.js +38 -0
- package/dist/prompt/rules/base.js.map +1 -0
- package/dist/prompt/rules/communication.d.ts +7 -0
- package/dist/prompt/rules/communication.d.ts.map +1 -0
- package/dist/prompt/rules/communication.js +53 -0
- package/dist/prompt/rules/communication.js.map +1 -0
- package/dist/prompt/rules/tool-strategy.d.ts +6 -0
- package/dist/prompt/rules/tool-strategy.d.ts.map +1 -0
- package/dist/prompt/rules/tool-strategy.js +42 -0
- package/dist/prompt/rules/tool-strategy.js.map +1 -0
- package/dist/prompt/rules/ui-control.d.ts +10 -0
- package/dist/prompt/rules/ui-control.d.ts.map +1 -0
- package/dist/prompt/rules/ui-control.js +44 -0
- package/dist/prompt/rules/ui-control.js.map +1 -0
- package/dist/prompt/types.d.ts +85 -0
- package/dist/prompt/types.d.ts.map +1 -0
- package/dist/prompt/types.js +10 -0
- package/dist/prompt/types.js.map +1 -0
- package/dist/realtime/long-poll.d.ts +21 -0
- package/dist/realtime/long-poll.d.ts.map +1 -0
- package/dist/realtime/long-poll.js +36 -0
- package/dist/realtime/long-poll.js.map +1 -0
- package/dist/runtime/config.d.ts +4 -0
- package/dist/runtime/config.d.ts.map +1 -0
- package/dist/runtime/config.js +29 -0
- package/dist/runtime/config.js.map +1 -0
- package/dist/runtime/turn-runner.d.ts +32 -0
- package/dist/runtime/turn-runner.d.ts.map +1 -0
- package/dist/runtime/turn-runner.js +216 -0
- package/dist/runtime/turn-runner.js.map +1 -0
- package/dist/runtime/types.d.ts +244 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +2 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/testing/desk-events.d.ts +12 -0
- package/dist/testing/desk-events.d.ts.map +1 -0
- package/dist/testing/desk-events.js +62 -0
- package/dist/testing/desk-events.js.map +1 -0
- package/dist/testing/index.d.ts +8 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +7 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/openapi-fixture.d.ts +47 -0
- package/dist/testing/openapi-fixture.d.ts.map +1 -0
- package/dist/testing/openapi-fixture.js +370 -0
- package/dist/testing/openapi-fixture.js.map +1 -0
- package/dist/tools/async-binding.d.ts +71 -0
- package/dist/tools/async-binding.d.ts.map +1 -0
- package/dist/tools/async-binding.js +104 -0
- package/dist/tools/async-binding.js.map +1 -0
- package/dist/tools/builtin-tools.d.ts +20 -0
- package/dist/tools/builtin-tools.d.ts.map +1 -0
- package/dist/tools/builtin-tools.js +108 -0
- package/dist/tools/builtin-tools.js.map +1 -0
- package/dist/tools/entity-tools.d.ts +6 -0
- package/dist/tools/entity-tools.d.ts.map +1 -0
- package/dist/tools/entity-tools.js +227 -0
- package/dist/tools/entity-tools.js.map +1 -0
- package/dist/tools/input-validation.d.ts +16 -0
- package/dist/tools/input-validation.d.ts.map +1 -0
- package/dist/tools/input-validation.js +75 -0
- package/dist/tools/input-validation.js.map +1 -0
- package/dist/tools/load-all-tools.d.ts +12 -0
- package/dist/tools/load-all-tools.d.ts.map +1 -0
- package/dist/tools/load-all-tools.js +42 -0
- package/dist/tools/load-all-tools.js.map +1 -0
- package/dist/tools/schema-loader.d.ts +24 -0
- package/dist/tools/schema-loader.d.ts.map +1 -0
- package/dist/tools/schema-loader.js +267 -0
- package/dist/tools/schema-loader.js.map +1 -0
- package/dist/tools/types.d.ts +85 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +22 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/turn-policy.d.ts +68 -0
- package/dist/turn-policy.d.ts.map +1 -0
- package/dist/turn-policy.js +21 -0
- package/dist/turn-policy.js.map +1 -0
- package/dist/types.d.ts +151 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/channel.d.ts +64 -0
- package/dist/ui/channel.d.ts.map +1 -0
- package/dist/ui/channel.js +81 -0
- package/dist/ui/channel.js.map +1 -0
- package/dist/ui/knowledge.d.ts +30 -0
- package/dist/ui/knowledge.d.ts.map +1 -0
- package/dist/ui/knowledge.js +66 -0
- package/dist/ui/knowledge.js.map +1 -0
- package/dist/ui/observations.d.ts +41 -0
- package/dist/ui/observations.d.ts.map +1 -0
- package/dist/ui/observations.js +236 -0
- package/dist/ui/observations.js.map +1 -0
- package/dist/ui/snapshot.d.ts +33 -0
- package/dist/ui/snapshot.d.ts.map +1 -0
- package/dist/ui/snapshot.js +75 -0
- package/dist/ui/snapshot.js.map +1 -0
- package/dist/ui/ui-sequence.d.ts +75 -0
- package/dist/ui/ui-sequence.d.ts.map +1 -0
- package/dist/ui/ui-sequence.js +73 -0
- package/dist/ui/ui-sequence.js.map +1 -0
- package/dist/ui/ui-tools.d.ts +59 -0
- package/dist/ui/ui-tools.d.ts.map +1 -0
- package/dist/ui/ui-tools.js +244 -0
- package/dist/ui/ui-tools.js.map +1 -0
- package/package.json +78 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Closure Studio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# @ouispec/agent-worker
|
|
2
|
+
|
|
3
|
+
The agent runtime. One core runs a turn: the model, the tool loop, UI actions answered by the user's own tab (ADR-0209), streaming, retries and deadlines. Two host adapters run that core: Lambda + SQS, and a long-running container (ADR-0227 §2.1).
|
|
4
|
+
|
|
5
|
+
## What the host supplies
|
|
6
|
+
|
|
7
|
+
A missing value fails when the adapter is created, and the error names it. Nothing falls back to an environment variable or an empty string.
|
|
8
|
+
|
|
9
|
+
| Seam | Config | Notes |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| Model | `model` | Any `ai` library model: `bedrock(...)`, `openai(...)`, `anthropic(...)`, or a gateway id. The worker imports no provider itself. |
|
|
12
|
+
| Prompt cache | `promptCacheBreakpoint`, optional | Provider options that mark a cache breakpoint. `PROMPT_CACHE_BREAKPOINTS.bedrock` and `.anthropic` are provided. Omit it for a provider that caches on its own. |
|
|
13
|
+
| Realtime server | `realtime: { url, apiKey }` | The `agent-sdk-realtime` server and its internal key. Turn events go to `POST {url}/api/emit`, and UI action answers are collected from `GET {url}/internal/oui/action-results/:id`. |
|
|
14
|
+
| Persona | `persona` or `systemPrompt`, exactly one | The assistant's identity comes only from here. The shipped rules name no product. |
|
|
15
|
+
| Tools | `tools` | A list, or a per-turn resolver. Pass `[]` when the only tools are the tab's UI actions. |
|
|
16
|
+
| Persistence | `getDb`, `getHistory`, `persistMessages` | `getDb`'s handle is passed to every callback. `recordTurnStart`, `recordTurnComplete` and `recordTurnFailure` are optional. |
|
|
17
|
+
|
|
18
|
+
Optional tuning, with its defaults:
|
|
19
|
+
- `maxRounds`: 12
|
|
20
|
+
- `maxTokens`: 4096
|
|
21
|
+
- `temperature`: 0.3
|
|
22
|
+
- `toolTimeoutMs`: 30 s
|
|
23
|
+
- `turnDeadlineMs`: 14 min (keep it below your host's own limit)
|
|
24
|
+
- `retries`: 3
|
|
25
|
+
|
|
26
|
+
Policy hooks: `turnPolicy`, `toolPolicy`, `uiActions`, `approvals`, `logger`.
|
|
27
|
+
|
|
28
|
+
## What a UI client sends with a turn
|
|
29
|
+
|
|
30
|
+
Two keys on the turn's `context` are for the worker itself, and your persona or `systemPrompt` callback never sees them:
|
|
31
|
+
|
|
32
|
+
| Key | What the tab puts there | What the worker does with it |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| `oui` | Its surface snapshot: `ouiRuntime.snapshot()` (ADR-0209) | Builds the turn's UI tools from it, and from nothing else |
|
|
35
|
+
| `uiKnowledge` | Its generated knowledge for the page the user is on: `resolveKnowledge(generatedKnowledge, path)` from `@ouispec/bindings` | Renders it after your prompt: each entry under "Platform Knowledge", then each recipe under "Active Workflows" |
|
|
36
|
+
|
|
37
|
+
Every other key is the page's context, which a persona prompt shows as the current UI state. A malformed `oui` or `uiKnowledge` fails the turn, naming the bad field. The worker logs `UI surfaces for this turn` with the surface ids and the knowledge's entry and workflow counts.
|
|
38
|
+
|
|
39
|
+
## Approvals (ADR-0228)
|
|
40
|
+
|
|
41
|
+
An irreversible action runs only on an approval the user gave, bound to the exact call. A call needs approval when:
|
|
42
|
+
|
|
43
|
+
- its declared `effect` is a `transaction` (an order, a payment, a send, a publish), which no policy can waive;
|
|
44
|
+
- its declaration says `destructive: true` (a UI action's `confirm`, or a host tool's `destructive`);
|
|
45
|
+
- or your `toolPolicy` returns `require_approval`. The policy's context carries the tool's `effect` and `destructive`, so it can decide on what the call does rather than on its name.
|
|
46
|
+
|
|
47
|
+
A `deny` always wins, over an approval too, and an approved call still runs as the user.
|
|
48
|
+
|
|
49
|
+
What happens:
|
|
50
|
+
|
|
51
|
+
1. The first such call in a turn stops the turn. The worker stores it in the realtime server's approval store and emits `agent:approval_required` with the preview. The preview's words come from the declaration: the tool's `title`, its `consequence` or description, and its input schema's property titles. The model is told only that the call is waiting, and never sees a token. Any other call in the same response is not run.
|
|
52
|
+
2. The user decides on the approval card.
|
|
53
|
+
3. The next turn carries `approval: { approvalId, decision: 'approve', token }` or `{ approvalId, decision: 'decline' }` on its payload, outside the message text. The worker redeems the token and runs exactly the stored call. A UI action's request then carries `approval: { approvalId, argsHash }` for the browser's own check. The call and its result are persisted, and the model is told what happened in an `<approval>` block. A replayed, expired or foreign token runs nothing, and neither does a decline; the model is told why.
|
|
54
|
+
|
|
55
|
+
In a browser, the turn after the decision comes from `ApprovalCard` (`@ouispec/agent-react`). Your `sendMessage` callback receives `approval` beside an empty `content`. Pass it unchanged to the worker as the turn payload's `approval`: `{ approvalId, decision: 'approve', token }` or `{ approvalId, decision: 'decline' }`. A UI action declares its effect on its OUI surface (`effect: 'transaction'`, or `confirm: true` for a destructive one). The tab's OUI runtime runs it only on the grant from the user's own click, which `grantApproval` in the client config hands it.
|
|
56
|
+
|
|
57
|
+
Declare a host tool's approval needs on the tool itself: `effect`, `destructive`, `title`, `consequence`, and `argsSensitive: false` when its arguments may be logged. Without an approval store, such a call is refused and never runs. The runtime uses the realtime server's store unless you pass `approvals.store`.
|
|
58
|
+
|
|
59
|
+
## Lambda + SQS
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { createLambdaAgentHandler, PROMPT_CACHE_BREAKPOINTS } from '@ouispec/agent-worker';
|
|
63
|
+
import { bedrock } from '@ai-sdk/amazon-bedrock';
|
|
64
|
+
|
|
65
|
+
export const handler = createLambdaAgentHandler({
|
|
66
|
+
model: bedrock('us.anthropic.claude-sonnet-4-6'),
|
|
67
|
+
promptCacheBreakpoint: PROMPT_CACHE_BREAKPOINTS.bedrock,
|
|
68
|
+
realtime: { url: env.REALTIME_URL, apiKey: env.REALTIME_INTERNAL_KEY },
|
|
69
|
+
persona,
|
|
70
|
+
tools: [],
|
|
71
|
+
getDb,
|
|
72
|
+
getHistory,
|
|
73
|
+
persistMessages,
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Each invocation takes one SQS record whose body is an `AgentTurnPayload`: `turnId`, `conversationId`, `userId`, `accountId`, `socketRoom`, `content`, and optionally `context` and `userToken`. A failure the runtime marks unrecoverable is rethrown, so your redrive policy sees it. A recoverable one, where the provider is rate-limiting or unavailable, has already been reported to the client and is not rethrown.
|
|
78
|
+
|
|
79
|
+
## Container
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { startContainerAgentWorker } from '@ouispec/agent-worker';
|
|
83
|
+
import { openai } from '@ai-sdk/openai';
|
|
84
|
+
|
|
85
|
+
const worker = await startContainerAgentWorker({
|
|
86
|
+
model: openai('gpt-4.1'),
|
|
87
|
+
realtime: { url: env.REALTIME_URL, apiKey: env.REALTIME_INTERNAL_KEY },
|
|
88
|
+
systemPrompt: () => persona,
|
|
89
|
+
tools: [],
|
|
90
|
+
getDb,
|
|
91
|
+
getHistory,
|
|
92
|
+
persistMessages,
|
|
93
|
+
port: 8080,
|
|
94
|
+
workerApiKey: env.WORKER_KEY,
|
|
95
|
+
});
|
|
96
|
+
process.on('SIGTERM', () => worker.close()); // closing waits for running turns
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Your API sends turns to the worker's HTTP endpoints:
|
|
100
|
+
|
|
101
|
+
| Request | Responses |
|
|
102
|
+
|---|---|
|
|
103
|
+
| `POST /turns` with an `AgentTurnPayload` and `x-api-key` | `202` once accepted. `400` if the body is not a turn, `401` for a wrong key, `409` if that turn is already running, `503` at capacity (`maxConcurrentTurns`, default 8) or while shutting down. |
|
|
104
|
+
| `GET /health` | The worker's status, running turns, capacity and model. |
|
|
105
|
+
|
|
106
|
+
`onTurnEnd` receives each turn's outcome.
|
|
107
|
+
|
|
108
|
+
## Conversation history in the browser
|
|
109
|
+
|
|
110
|
+
`agent-sdk-react` restores a tab's conversation after a reload, and lists or switches conversations, when your API provides two endpoints behind `AgentClientConfig`. Both must be filtered by the signed-in user:
|
|
111
|
+
|
|
112
|
+
- `listConversations`: the user's conversations, most recent first.
|
|
113
|
+
- `getConversation`: one of them, with its messages.
|
|
114
|
+
|
|
115
|
+
## API tools from an OpenAPI document
|
|
116
|
+
|
|
117
|
+
`loadOpenApiTools` turns the product's own OpenAPI 3 document into tools for backend, MCP and external agents (ADR-0181 §2). There is no second manifest format. It is also exported at `@ouispec/agent-worker/openapi`.
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import { loadOpenApiTools } from '@ouispec/agent-worker/openapi';
|
|
121
|
+
|
|
122
|
+
const tools = loadOpenApiTools(document, {
|
|
123
|
+
baseUrl: 'https://api.example.com',
|
|
124
|
+
audience: 'agents', // or 'pa': only operations flagged pa: true
|
|
125
|
+
actAs: (ctx) => signedAssertionFor(ctx.userId, ctx.accountId), // headers; required, no default
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
An operation opts in per operation, with `x-agent`:
|
|
130
|
+
|
|
131
|
+
| Key | Meaning |
|
|
132
|
+
|---|---|
|
|
133
|
+
| `expose: true` | The operation is a tool. Without it, it is not offered. |
|
|
134
|
+
| `effect` | What calling it changes (ADR-0226 §2.6): `view` (a read), `file`, `mutate`, `job` or `transaction`, or the effect's object form. `selection`, `navigate`, `open` and `edit` are a page's effects and are refused. |
|
|
135
|
+
| `destructive: true` | On a write: it removes or replaces something the person made. |
|
|
136
|
+
| `consequence` | A sentence saying what happens. It goes in the description, and in an approval's preview. |
|
|
137
|
+
| `pa: true` | The product's own assistant may call it. Allowed only on a read (ADR-0227 §2.1). |
|
|
138
|
+
|
|
139
|
+
Each tool:
|
|
140
|
+
- is named by the operation's `operationId`, and described by its `summary` and `description`;
|
|
141
|
+
- takes its parameters and JSON body as input, with `$ref`s inlined, 3.0 `nullable` read as `null`, and `readOnly` fields left out. A closed object body's fields sit beside the parameters; any other body is the `body` property. The input refuses undeclared properties, and the worker validates every call against it;
|
|
142
|
+
- carries `effect` and `destructive` for approvals to key on, and its operation's standard `security` requirement as the permissions it needs;
|
|
143
|
+
- calls the API with the headers `actAs` returns, so the API treats the call as that caller. A permission the caller lacks comes back as the route's own refusal. The tool returns it as its error, with the status and body, and the model reads it;
|
|
144
|
+
- runs a `transaction` only with the person's approval of that exact call (ADR-0228): the worker's approval card supplies it, and its title and consequence come from the operation's `summary` and `x-agent.consequence`. Without one, as over MCP, it returns `APPROVAL_REQUIRED` and sends nothing.
|
|
145
|
+
|
|
146
|
+
Refused at load, naming the operation: `pa` on anything but a read, a missing or invalid `operationId`, a missing `effect`, an unknown `x-agent` key, `destructive` on a read, a read on `PUT`, `PATCH` or `DELETE`, no security requirement (use `[]` for a public operation), an undeclared security scheme, a non-JSON body, a self-referencing or external `$ref`, and a path whose template and parameters disagree.
|
|
147
|
+
|
|
148
|
+
**Async operations (W9).** An operation that starts a job names the declared completion it ends with, in `x-async-binding`:
|
|
149
|
+
|
|
150
|
+
```yaml
|
|
151
|
+
x-agent: { expose: true, effect: job }
|
|
152
|
+
x-async-binding: { event: report:ready, timeout: 2m } # optional: failure, correlation, idField
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- `event` must be a declared completion in the product's event declarations, which are passed as `events`. Its failure and the event's id field (`correlation`) default to what the declarations pair with it.
|
|
156
|
+
- The tool reads the job id from the response field named by the event's correlation. When the route returns it under another name, `idField` names that field.
|
|
157
|
+
- The wait defaults to the `job` effect's `timeoutMs`, else 60 s.
|
|
158
|
+
- An undeclared or non-completion event is refused at load. So are a binding on an operation whose effect isn't `job` or `transaction`, and a binding when no `events` are given.
|
|
159
|
+
- With a `waiter` (e.g. `createHttpEventWaiter`), the tool waits for the job to settle and returns `status: 'complete'` with the declared result. A failure fails the call with its declared reason. If the job hasn't settled in time, the call returns `status: 'dispatched'` with the reason. Without a waiter it returns the dispatch.
|
|
160
|
+
- The binding's `room`, `payload` and `lifecycleEvents` are documentation: the declarations are the authority.
|
|
161
|
+
|
|
162
|
+
Header parameters are never model input: `actAs` supplies them, and a call fails if it leaves out one the operation requires.
|
|
163
|
+
|
|
164
|
+
`@ouispec/agent-worker/testing` ships a fixture product's API for tests: an OpenAPI document, its event declarations (`DESK_EVENTS`), and a server that enforces its own auth and permissions. Its report export is a job, and `onReportQueued` lets a test end it with a declared event.
|
|
165
|
+
|
|
166
|
+
## The socket in the browser
|
|
167
|
+
|
|
168
|
+
`agent-sdk-react` connects to the realtime server through `realtime.createSocket`. The default is a Socket.IO client. Any `SocketLike` works: it must emit `connect` on every (re)connection and `disconnect` when it drops. That same socket carries the tab's OUI transport.
|
|
169
|
+
|
|
170
|
+
## Testing
|
|
171
|
+
|
|
172
|
+
- `pnpm test` includes the fixture turn. It runs on both adapters and on two providers, replaying recorded responses through each provider's real `ai` package, against the SDK realtime server on Redis.
|
|
173
|
+
- `pnpm record:fixture-turn` re-records the Bedrock responses from a live model.
|
|
174
|
+
- `pnpm eval:live` runs the live operating-rules eval.
|
|
175
|
+
- Both of those live commands need Bedrock credentials in the environment.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The worker's side of the approval store (ADR-0228 §2.4), which lives in the
|
|
3
|
+
* realtime server beside the UI action result store. The worker stores a
|
|
4
|
+
* pending approval when it stops a turn, redeems the token the next turn
|
|
5
|
+
* carries, and reads a decline.
|
|
6
|
+
*/
|
|
7
|
+
import type { ApprovalRedeemResult, ApprovalStatus, PendingApprovalInput } from '@ouispec/agent-core';
|
|
8
|
+
export interface ApprovalStoreClient {
|
|
9
|
+
/** Store a pending approval. Rejects when it was not stored: the call must then not wait on it. */
|
|
10
|
+
create(pending: PendingApprovalInput): Promise<void>;
|
|
11
|
+
/** The stored call for a valid, unexpired, unused token of this user and conversation; otherwise why not. Rejects when the store cannot be reached. */
|
|
12
|
+
redeem(token: string, caller: {
|
|
13
|
+
userId: string;
|
|
14
|
+
conversationId: string;
|
|
15
|
+
}): Promise<ApprovalRedeemResult>;
|
|
16
|
+
/** Where an approval of this user's stands, or null when there is none. */
|
|
17
|
+
status(approvalId: string, userId: string): Promise<ApprovalStatus | null>;
|
|
18
|
+
}
|
|
19
|
+
export interface HttpApprovalStoreClientConfig {
|
|
20
|
+
/** The realtime server's base URL. */
|
|
21
|
+
url: string;
|
|
22
|
+
/** Its internal key. */
|
|
23
|
+
apiKey: string;
|
|
24
|
+
/** Per request. Default 5 s. */
|
|
25
|
+
timeoutMs?: number;
|
|
26
|
+
}
|
|
27
|
+
/** The store over the realtime server's internal HTTP API. */
|
|
28
|
+
export declare function createHttpApprovalStoreClient(config: HttpApprovalStoreClientConfig): ApprovalStoreClient;
|
|
29
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/approvals/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EACV,oBAAoB,EAEpB,cAAc,EAEd,oBAAoB,EACrB,MAAM,qBAAqB,CAAC;AAG7B,MAAM,WAAW,mBAAmB;IAClC,mGAAmG;IACnG,MAAM,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,uJAAuJ;IACvJ,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACzG,2EAA2E;IAC3E,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;CAC5E;AAED,MAAM,WAAW,6BAA6B;IAC5C,sCAAsC;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,8DAA8D;AAC9D,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,6BAA6B,GAAG,mBAAmB,CA8CxG"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { requireRealtime } from '../emit/http-adapter.js';
|
|
2
|
+
/** The store over the realtime server's internal HTTP API. */
|
|
3
|
+
export function createHttpApprovalStoreClient(config) {
|
|
4
|
+
const { url, apiKey } = config;
|
|
5
|
+
requireRealtime(url, apiKey);
|
|
6
|
+
const timeoutMs = config.timeoutMs ?? 5_000;
|
|
7
|
+
const call = async (path, body) => {
|
|
8
|
+
const res = await fetch(`${url}${path}`, {
|
|
9
|
+
method: body === undefined ? 'GET' : 'POST',
|
|
10
|
+
headers: { 'Content-Type': 'application/json', 'X-Api-Key': apiKey },
|
|
11
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
12
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
13
|
+
});
|
|
14
|
+
const text = await res.text();
|
|
15
|
+
let json = {};
|
|
16
|
+
try {
|
|
17
|
+
json = text ? JSON.parse(text) : {};
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// A non-JSON answer is reported by status below.
|
|
21
|
+
}
|
|
22
|
+
return { status: res.status, json };
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
async create(pending) {
|
|
26
|
+
const { status, json } = await call('/internal/approvals', pending);
|
|
27
|
+
if (status !== 201) {
|
|
28
|
+
throw new Error(`[agent-sdk] The approval store did not keep approval ${pending.approvalId}: HTTP ${status} ${String(json.error ?? '')}`);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
async redeem(token, caller) {
|
|
32
|
+
const { status, json } = await call('/internal/approvals/redeem', { token, ...caller });
|
|
33
|
+
if (status === 200)
|
|
34
|
+
return { ok: true, call: json.call };
|
|
35
|
+
if (status >= 400 && status < 500 && typeof json.reason === 'string') {
|
|
36
|
+
return { ok: false, reason: json.reason, error: String(json.error ?? json.reason) };
|
|
37
|
+
}
|
|
38
|
+
throw new Error(`[agent-sdk] The approval store could not redeem a token: HTTP ${status}`);
|
|
39
|
+
},
|
|
40
|
+
async status(approvalId, userId) {
|
|
41
|
+
const { status, json } = await call(`/internal/approvals/${encodeURIComponent(approvalId)}?${new URLSearchParams({ userId })}`);
|
|
42
|
+
if (status === 200)
|
|
43
|
+
return json;
|
|
44
|
+
if (status === 404)
|
|
45
|
+
return null;
|
|
46
|
+
throw new Error(`[agent-sdk] The approval store could not report approval ${approvalId}: HTTP ${status}`);
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/approvals/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The turn after a decision (ADR-0228 §2.2.5). It carries, outside the
|
|
3
|
+
* message text, the token to redeem or that the user declined. The worker
|
|
4
|
+
* asks the store — never the client — what was decided, runs exactly the
|
|
5
|
+
* stored call when there is one, and tells the model what happened in an
|
|
6
|
+
* `<approval>` block on the user's message.
|
|
7
|
+
*/
|
|
8
|
+
import type { ApprovalContinuation, ApprovedCall } from '@ouispec/agent-core';
|
|
9
|
+
import type { ApprovalStoreClient } from './client.js';
|
|
10
|
+
export type ContinuationOutcome =
|
|
11
|
+
/** Approved and redeemed: run this call, exactly. */
|
|
12
|
+
{
|
|
13
|
+
kind: 'run';
|
|
14
|
+
call: ApprovedCall;
|
|
15
|
+
}
|
|
16
|
+
/** Nothing runs; the model is told why. */
|
|
17
|
+
| {
|
|
18
|
+
kind: 'note';
|
|
19
|
+
note: string;
|
|
20
|
+
};
|
|
21
|
+
export declare const approvalNote: (text: string) => string;
|
|
22
|
+
export declare function ranNote(title: string): string;
|
|
23
|
+
export declare function unavailableNote(title: string): string;
|
|
24
|
+
export declare function resolveContinuation(store: ApprovalStoreClient | undefined, continuation: ApprovalContinuation, turn: {
|
|
25
|
+
userId: string;
|
|
26
|
+
conversationId: string;
|
|
27
|
+
}): Promise<ContinuationOutcome>;
|
|
28
|
+
//# sourceMappingURL=continuation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"continuation.d.ts","sourceRoot":"","sources":["../../src/approvals/continuation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,oBAAoB,EAAyB,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACrG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,MAAM,MAAM,mBAAmB;AAC7B,qDAAqD;AACnD;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,YAAY,CAAA;CAAE;AACrC,2CAA2C;GACzC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAanC,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,WAAmC,CAAC;AAE7E,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,mBAAmB,GAAG,SAAS,EACtC,YAAY,EAAE,oBAAoB,EAClC,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,GAC/C,OAAO,CAAC,mBAAmB,CAAC,CAyB9B"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const WHY_NOT = {
|
|
2
|
+
used: 'this approval has already been used',
|
|
3
|
+
expired: 'the approval expired before it was used',
|
|
4
|
+
invalid: 'the approval is not valid',
|
|
5
|
+
forbidden: 'the approval belongs to another user or conversation',
|
|
6
|
+
mismatch: 'the approval is not for that call',
|
|
7
|
+
unknown: 'no such approval is waiting',
|
|
8
|
+
decided: 'the approval could not be used',
|
|
9
|
+
channel: 'the approval could not be used',
|
|
10
|
+
};
|
|
11
|
+
export const approvalNote = (text) => `<approval>${text}</approval>`;
|
|
12
|
+
export function ranNote(title) {
|
|
13
|
+
return approvalNote(`The user approved "${title}" on the approval card, and it was run once, with the arguments they approved. Its result follows: report what it says, including a failure.`);
|
|
14
|
+
}
|
|
15
|
+
export function unavailableNote(title) {
|
|
16
|
+
return approvalNote(`The user approved "${title}" on the approval card, but it did not run: it is not available where they are now.`);
|
|
17
|
+
}
|
|
18
|
+
export async function resolveContinuation(store, continuation, turn) {
|
|
19
|
+
if (!store)
|
|
20
|
+
return { kind: 'note', note: approvalNote('An approval arrived, but the approved action did not run: this worker has no approval store.') };
|
|
21
|
+
if (continuation.decision === 'decline') {
|
|
22
|
+
const status = await store.status(continuation.approvalId, turn.userId).catch(() => null);
|
|
23
|
+
const what = status?.title ? `"${status.title}"` : 'the action';
|
|
24
|
+
return {
|
|
25
|
+
kind: 'note',
|
|
26
|
+
note: status?.status === 'declined'
|
|
27
|
+
? approvalNote(`The user declined ${what} on the approval card, so it did not run. Do not run it again unless they ask for it again.`)
|
|
28
|
+
: approvalNote(`The user did not approve ${what}, so it did not run.`),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
let redeemed;
|
|
32
|
+
try {
|
|
33
|
+
redeemed = await store.redeem(continuation.token, turn);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return { kind: 'note', note: approvalNote('An approval arrived, but the approved action did not run: the approval could not be checked.') };
|
|
37
|
+
}
|
|
38
|
+
if (!redeemed.ok) {
|
|
39
|
+
return { kind: 'note', note: approvalNote(`An approval arrived, but the approved action did not run: ${WHY_NOT[redeemed.reason]}.`) };
|
|
40
|
+
}
|
|
41
|
+
return { kind: 'run', call: redeemed.call };
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=continuation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"continuation.js","sourceRoot":"","sources":["../../src/approvals/continuation.ts"],"names":[],"mappings":"AAgBA,MAAM,OAAO,GAA0C;IACrD,IAAI,EAAE,qCAAqC;IAC3C,OAAO,EAAE,yCAAyC;IAClD,OAAO,EAAE,2BAA2B;IACpC,SAAS,EAAE,sDAAsD;IACjE,QAAQ,EAAE,mCAAmC;IAC7C,OAAO,EAAE,6BAA6B;IACtC,OAAO,EAAE,gCAAgC;IACzC,OAAO,EAAE,gCAAgC;CAC1C,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,aAAa,IAAI,aAAa,CAAC;AAE7E,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,OAAO,YAAY,CAAC,sBAAsB,KAAK,8IAA8I,CAAC,CAAC;AACjM,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,OAAO,YAAY,CAAC,sBAAsB,KAAK,qFAAqF,CAAC,CAAC;AACxI,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,KAAsC,EACtC,YAAkC,EAClC,IAAgD;IAEhD,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,CAAC,8FAA8F,CAAC,EAAE,CAAC;IAExJ,IAAI,YAAY,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;QACxC,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QAC1F,MAAM,IAAI,GAAG,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC;QAChE,OAAO;YACL,IAAI,EAAE,MAAM;YACZ,IAAI,EACF,MAAM,EAAE,MAAM,KAAK,UAAU;gBAC3B,CAAC,CAAC,YAAY,CAAC,qBAAqB,IAAI,6FAA6F,CAAC;gBACtI,CAAC,CAAC,YAAY,CAAC,4BAA4B,IAAI,sBAAsB,CAAC;SAC3E,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,CAAC;IACb,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC1D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,CAAC,8FAA8F,CAAC,EAAE,CAAC;IAC9I,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,CAAC,6DAA6D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IACxI,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC;AAC9C,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The approval card's words (ADR-0228 §2.2.2). Every one comes from the
|
|
3
|
+
* tool's declaration — its title, its description, its input schema's labels —
|
|
4
|
+
* and the call's arguments. None comes from the model: the model chooses the
|
|
5
|
+
* call, never how it is described to the person approving it.
|
|
6
|
+
*/
|
|
7
|
+
import type { ApprovalPreview } from '@ouispec/agent-core';
|
|
8
|
+
import type { RegisteredTool } from '../tools/types.js';
|
|
9
|
+
/** The action's title and what it does, as its declaration states them. */
|
|
10
|
+
export declare function declaredTitle(tool: RegisteredTool): {
|
|
11
|
+
title: string;
|
|
12
|
+
consequence?: string;
|
|
13
|
+
};
|
|
14
|
+
export declare function buildApprovalPreview(tool: RegisteredTool, args: Record<string, unknown>): ApprovalPreview;
|
|
15
|
+
//# sourceMappingURL=preview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/approvals/preview.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,eAAe,EAA2B,MAAM,qBAAqB,CAAC;AACpF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAOxD,2EAA2E;AAC3E,wBAAgB,aAAa,CAAC,IAAI,EAAE,cAAc,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAI3F;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe,CAezG"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** The action's title and what it does, as its declaration states them. */
|
|
2
|
+
export function declaredTitle(tool) {
|
|
3
|
+
const title = tool.title?.trim() || tool.name;
|
|
4
|
+
const consequence = (tool.consequence ?? tool.description)?.trim();
|
|
5
|
+
return consequence && consequence !== title ? { title, consequence } : { title };
|
|
6
|
+
}
|
|
7
|
+
export function buildApprovalPreview(tool, args) {
|
|
8
|
+
const { title, consequence } = declaredTitle(tool);
|
|
9
|
+
const properties = (tool.inputSchema.properties ?? {});
|
|
10
|
+
// The schema's order first, then anything else the call carries.
|
|
11
|
+
const names = [...Object.keys(properties).filter((k) => k in args), ...Object.keys(args).filter((k) => !(k in properties))];
|
|
12
|
+
const argumentsShown = names
|
|
13
|
+
.filter((name) => args[name] !== undefined)
|
|
14
|
+
.map((name) => ({ name, label: properties[name]?.title?.trim() || name, value: valueText(args[name], properties[name]) }));
|
|
15
|
+
const listed = argumentsShown.map((a) => `${a.label} ${a.value}`).join(', ');
|
|
16
|
+
const readback = `${title}${listed ? `: ${listed}` : ''}.${consequence ? ` ${consequence}` : ''}`;
|
|
17
|
+
return { title, ...(consequence ? { consequence } : {}), arguments: argumentsShown, readback };
|
|
18
|
+
}
|
|
19
|
+
function valueText(value, schema) {
|
|
20
|
+
const named = schema?.oneOf?.find((o) => o.const === value && o.title);
|
|
21
|
+
if (named?.title)
|
|
22
|
+
return named.title;
|
|
23
|
+
if (value === null)
|
|
24
|
+
return 'none';
|
|
25
|
+
if (typeof value === 'string')
|
|
26
|
+
return value;
|
|
27
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
28
|
+
return String(value);
|
|
29
|
+
if (Array.isArray(value) && value.every((v) => v === null || typeof v !== 'object'))
|
|
30
|
+
return value.map((v) => String(v)).join(', ');
|
|
31
|
+
return JSON.stringify(value);
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=preview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.js","sourceRoot":"","sources":["../../src/approvals/preview.ts"],"names":[],"mappings":"AAcA,2EAA2E;AAC3E,MAAM,UAAU,aAAa,CAAC,IAAoB;IAChD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC;IAC9C,MAAM,WAAW,GAAG,CAAC,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,CAAC;IACnE,OAAO,WAAW,IAAI,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;AACnF,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,IAAoB,EAAE,IAA6B;IACtF,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IACnD,MAAM,UAAU,GAAG,CAAE,IAAI,CAAC,WAA+D,CAAC,UAAU,IAAI,EAAE,CAGzG,CAAC;IACF,iEAAiE;IACjE,MAAM,KAAK,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;IAC5H,MAAM,cAAc,GAA8B,KAAK;SACpD,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC;SAC1C,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,IAAI,EAAE,KAAK,EAAE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAE7H,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7E,MAAM,QAAQ,GAAG,GAAG,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,WAAW,CAAC,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IAClG,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC;AACjG,CAAC;AAED,SAAS,SAAS,CAAC,KAAc,EAAE,MAAkC;IACnE,MAAM,KAAK,GAAG,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC;IACvE,IAAI,KAAK,EAAE,KAAK;QAAE,OAAO,KAAK,CAAC,KAAK,CAAC;IACrC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAClC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IAClF,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnI,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RegisteredTool, ToolEffect } from '../tools/types.js';
|
|
2
|
+
export interface ApprovalRequirement {
|
|
3
|
+
effect: ToolEffect | undefined;
|
|
4
|
+
destructive: boolean;
|
|
5
|
+
/** Whether the declaration alone requires the user's approval of each call. */
|
|
6
|
+
required: boolean;
|
|
7
|
+
/** The effect as the approval store and its token name it: its kind, or `write` when none is declared. */
|
|
8
|
+
effectName: string;
|
|
9
|
+
/** How long an approval of it lasts. */
|
|
10
|
+
ttlMs: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function approvalRequirement(tool: RegisteredTool): ApprovalRequirement;
|
|
13
|
+
/** What the model reads on a tool whose calls need approval, in place of any "confirm first" instruction. */
|
|
14
|
+
export declare const APPROVAL_TOOL_NOTE: string;
|
|
15
|
+
//# sourceMappingURL=requirement.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requirement.d.ts","sourceRoot":"","sources":["../../src/approvals/requirement.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpE,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,UAAU,GAAG,SAAS,CAAC;IAC/B,WAAW,EAAE,OAAO,CAAC;IACrB,+EAA+E;IAC/E,QAAQ,EAAE,OAAO,CAAC;IAClB,0GAA0G;IAC1G,UAAU,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,cAAc,GAAG,mBAAmB,CAW7E;AAED,6GAA6G;AAC7G,eAAO,MAAM,kBAAkB,QAGX,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a tool's declaration says about approval (ADR-0228 §2.1), for every
|
|
3
|
+
* kind of tool the worker runs: a UI action from the client's surfaces, a
|
|
4
|
+
* generated API tool (its effect from the operation's `x-agent`), a host tool.
|
|
5
|
+
* The rule itself is OUI's `requiresApproval` (through oui-bindings), which the browser applies too; a host's
|
|
6
|
+
* policy may require approval of more, never of less.
|
|
7
|
+
*/
|
|
8
|
+
import { DEFAULT_APPROVAL_TTL_MS, MAX_APPROVAL_TTL_MS } from '@ouispec/agent-core';
|
|
9
|
+
import { effectKind, requiresApproval } from '@ouispec/bindings';
|
|
10
|
+
export function approvalRequirement(tool) {
|
|
11
|
+
const effect = tool.effect;
|
|
12
|
+
const destructive = tool.destructive === true;
|
|
13
|
+
const minutes = effect && typeof effect === 'object' && effect.kind === 'transaction' ? effect.approvalMinutes : undefined;
|
|
14
|
+
return {
|
|
15
|
+
effect,
|
|
16
|
+
destructive,
|
|
17
|
+
required: requiresApproval(effect, destructive),
|
|
18
|
+
effectName: effect === undefined ? 'write' : effectKind(effect),
|
|
19
|
+
ttlMs: minutes ? Math.min(minutes * 60_000, MAX_APPROVAL_TTL_MS) : DEFAULT_APPROVAL_TTL_MS,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** What the model reads on a tool whose calls need approval, in place of any "confirm first" instruction. */
|
|
23
|
+
export const APPROVAL_TOOL_NOTE = "Needs the user's approval: calling it shows them an approval card with exactly these arguments, and it runs only when " +
|
|
24
|
+
'they approve it there. Call it directly when they ask for it. Do not ask for a yes in chat first, and never treat a typed ' +
|
|
25
|
+
'yes as approval.';
|
|
26
|
+
//# sourceMappingURL=requirement.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requirement.js","sourceRoot":"","sources":["../../src/approvals/requirement.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AACnF,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAcjE,MAAM,UAAU,mBAAmB,CAAC,IAAoB;IACtD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC3B,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,KAAK,IAAI,CAAC;IAC9C,MAAM,OAAO,GAAG,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,KAAK,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3H,OAAO;QACL,MAAM;QACN,WAAW;QACX,QAAQ,EAAE,gBAAgB,CAAC,MAAM,EAAE,WAAW,CAAC;QAC/C,UAAU,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC;QAC/D,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,GAAG,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC,uBAAuB;KAC3F,CAAC;AACJ,CAAC;AAED,6GAA6G;AAC7G,MAAM,CAAC,MAAM,kBAAkB,GAC7B,wHAAwH;IACxH,4HAA4H;IAC5H,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ToolEffect } from '../tools/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* ToolPolicy — evaluated before every tool execution.
|
|
4
|
+
* Integrators provide their own policy implementation via AgentWorkerConfig.toolPolicy.
|
|
5
|
+
*
|
|
6
|
+
* The policy is called AFTER quota enforcement but BEFORE the tool executes.
|
|
7
|
+
* This allows integrators to enforce per-user, per-account, per-tool authorization
|
|
8
|
+
* rules (role-based access, feature flags, rate limits, compliance checks, etc.).
|
|
9
|
+
*/
|
|
10
|
+
export interface ToolPolicy {
|
|
11
|
+
evaluate(ctx: ToolPolicyContext): Promise<ToolPolicyDecision>;
|
|
12
|
+
}
|
|
13
|
+
export interface ToolPolicyContext {
|
|
14
|
+
/** The internal user ID performing the action. */
|
|
15
|
+
userId: string;
|
|
16
|
+
/** The account ID the user belongs to. */
|
|
17
|
+
accountId: string;
|
|
18
|
+
/** The tool being invoked. */
|
|
19
|
+
toolName: string;
|
|
20
|
+
/** The arguments passed to the tool. */
|
|
21
|
+
args: Record<string, unknown>;
|
|
22
|
+
/** The current turn ID for audit/traceability. */
|
|
23
|
+
turnId: string;
|
|
24
|
+
/** Whether the tool has side effects (fail-closed: true if unknown). */
|
|
25
|
+
hasSideEffects: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* `ui` for an action on the client's UI (ADR-0209), which runs in the
|
|
28
|
+
* user's browser under their own session; `backend` for a host tool.
|
|
29
|
+
* Decide by this, not by the name: a UI action id can equal a host tool's.
|
|
30
|
+
*/
|
|
31
|
+
toolKind: 'ui' | 'backend';
|
|
32
|
+
/** What the tool declares it does (ADR-0226 §2.6); undefined when it declares nothing, which counts as a write. */
|
|
33
|
+
effect: ToolEffect | undefined;
|
|
34
|
+
/** Whether the tool declares it removes or replaces something the person made. */
|
|
35
|
+
destructive: boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* - `allow`: run it. A `transaction` or destructive call still needs the
|
|
39
|
+
* user's approval (ADR-0228 §2.1): no policy waives that.
|
|
40
|
+
* - `deny`: never run it. Approval never overrides a deny.
|
|
41
|
+
* - `require_approval`: run it only on the user's approval of this exact call,
|
|
42
|
+
* given on the approval card (or a conversation channel's readback). The
|
|
43
|
+
* turn stops at it; the call runs in the turn after the decision.
|
|
44
|
+
*/
|
|
45
|
+
export type ToolPolicyDecision = {
|
|
46
|
+
action: 'allow';
|
|
47
|
+
} | {
|
|
48
|
+
action: 'deny';
|
|
49
|
+
reason: string;
|
|
50
|
+
} | {
|
|
51
|
+
action: 'require_approval';
|
|
52
|
+
reason: string;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Default policy — allows all tools. Integrators override this.
|
|
56
|
+
*/
|
|
57
|
+
export declare const defaultToolPolicy: ToolPolicy;
|
|
58
|
+
/**
|
|
59
|
+
* Evaluate a tool policy, returning a denial on error (fail-closed).
|
|
60
|
+
* This wrapper ensures that a broken policy never silently allows execution.
|
|
61
|
+
*/
|
|
62
|
+
export declare function evaluateToolPolicySafe(policy: ToolPolicy, ctx: ToolPolicyContext): Promise<ToolPolicyDecision>;
|
|
63
|
+
//# sourceMappingURL=tool-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-policy.d.ts","sourceRoot":"","sources":["../../src/authz/tool-policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD;;;;;;;GAOG;AAEH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,iBAAiB;IAChC,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,cAAc,EAAE,OAAO,CAAC;IACxB;;;;OAIG;IACH,QAAQ,EAAE,IAAI,GAAG,SAAS,CAAC;IAC3B,mHAAmH;IACnH,MAAM,EAAE,UAAU,GAAG,SAAS,CAAC;IAC/B,kFAAkF;IAClF,WAAW,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GACnB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,MAAM,EAAE,kBAAkB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD;;GAEG;AACH,eAAO,MAAM,iBAAiB,EAAE,UAE/B,CAAC;AAEF;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,iBAAiB,GACrB,OAAO,CAAC,kBAAkB,CAAC,CAU7B"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default policy — allows all tools. Integrators override this.
|
|
3
|
+
*/
|
|
4
|
+
export const defaultToolPolicy = {
|
|
5
|
+
evaluate: async () => ({ action: 'allow' }),
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Evaluate a tool policy, returning a denial on error (fail-closed).
|
|
9
|
+
* This wrapper ensures that a broken policy never silently allows execution.
|
|
10
|
+
*/
|
|
11
|
+
export async function evaluateToolPolicySafe(policy, ctx) {
|
|
12
|
+
try {
|
|
13
|
+
return await policy.evaluate(ctx);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
const message = error instanceof Error ? error.message : 'Unknown policy error';
|
|
17
|
+
return {
|
|
18
|
+
action: 'deny',
|
|
19
|
+
reason: `Policy evaluation failed (fail-closed): ${message}`,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=tool-policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-policy.js","sourceRoot":"","sources":["../../src/authz/tool-policy.ts"],"names":[],"mappings":"AAqDA;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAe;IAC3C,QAAQ,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,OAAgB,EAAE,CAAC;CACrD,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,MAAkB,EAClB,GAAsB;IAEtB,IAAI,CAAC;QACH,OAAO,MAAM,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,sBAAsB,CAAC;QAChF,OAAO;YACL,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,2CAA2C,OAAO,EAAE;SAC7D,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type TurnOutcome } from '../runtime/turn-runner.js';
|
|
2
|
+
import type { AgentRuntimeConfig } from '../runtime/types.js';
|
|
3
|
+
export interface ContainerAgentConfig<TDb> extends AgentRuntimeConfig<TDb> {
|
|
4
|
+
/** The port to listen on. `0` picks a free one. Required. */
|
|
5
|
+
port: number;
|
|
6
|
+
/** The key the host's API sends as `x-api-key`. Required: the endpoint runs turns as any user. */
|
|
7
|
+
workerApiKey: string;
|
|
8
|
+
/** Turns run at once; more are refused with 503. Default 8. */
|
|
9
|
+
maxConcurrentTurns?: number;
|
|
10
|
+
/** Largest request body, in bytes. Default 1 MiB. */
|
|
11
|
+
maxBodyBytes?: number;
|
|
12
|
+
/** Called with each turn's outcome, after it ends. */
|
|
13
|
+
onTurnEnd?: (outcome: TurnOutcome) => void;
|
|
14
|
+
}
|
|
15
|
+
export interface ContainerAgentWorker {
|
|
16
|
+
/** The port the server listens on (the one picked, when configured with 0). */
|
|
17
|
+
port: number;
|
|
18
|
+
/** Turns running now. */
|
|
19
|
+
running(): number;
|
|
20
|
+
/** Stops taking turns, waits for the running ones to end, then closes. */
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
export declare function startContainerAgentWorker<TDb>(config: ContainerAgentConfig<TDb>): Promise<ContainerAgentWorker>;
|
|
24
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/container/server.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAyC,KAAK,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACpG,OAAO,KAAK,EAAE,kBAAkB,EAAoB,MAAM,qBAAqB,CAAC;AAEhF,MAAM,WAAW,oBAAoB,CAAC,GAAG,CAAE,SAAQ,kBAAkB,CAAC,GAAG,CAAC;IACxE,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,kGAAkG;IAClG,YAAY,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,qDAAqD;IACrD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sDAAsD;IACtD,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,oBAAoB;IACnC,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,OAAO,IAAI,MAAM,CAAC;IAClB,0EAA0E;IAC1E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAsB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,oBAAoB,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA2GrH"}
|