@dudousxd/nestjs-agent-opencode 1.0.0 → 1.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/README.md +88 -0
- package/dist/durable/index.cjs +613 -39
- package/dist/durable/index.cjs.map +1 -1
- package/dist/durable/index.d.cts +11 -5
- package/dist/durable/index.d.ts +11 -5
- package/dist/durable/index.js +613 -39
- package/dist/durable/index.js.map +1 -1
- package/dist/{engine-BX6MLzxs.d.cts → engine-yFh3eD8j.d.cts} +241 -7
- package/dist/{engine-BX6MLzxs.d.ts → engine-yFh3eD8j.d.ts} +241 -7
- package/dist/index.cjs +595 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +594 -26
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -120,6 +120,94 @@ of settling it as an empty success. It needs the client's `message.list`. `openC
|
|
|
120
120
|
same steps in memory (single replica). Several processes need a cross-process sink and a persistent
|
|
121
121
|
`OpenCodeSessionStore`.
|
|
122
122
|
|
|
123
|
+
### Limits, stops and lifecycle hooks
|
|
124
|
+
|
|
125
|
+
Everything here is optional; without these options a turn runs exactly as before. They work with
|
|
126
|
+
`openCode()` too.
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
openCodeDurable({
|
|
130
|
+
host: MyOpenCodeHost,
|
|
131
|
+
// Per engine, or `(input) => limits` per turn.
|
|
132
|
+
limits: { steps: 40, compactions: 2, wallClockMs: 10 * 60_000 },
|
|
133
|
+
onTurnStart: ({ turnId, threadId, sessionId, resumed }) => turnState.open(turnId),
|
|
134
|
+
onTurnEvent: ({ turnId, type, sessionId }) => turnState.saw(turnId, type),
|
|
135
|
+
onStop: ({ turnId, reason, counts }) => credentials.revoke(turnId),
|
|
136
|
+
onTurnEnd: ({ turnId, status, stop }) => turnState.close(turnId, status, stop?.reason),
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// A stop the app starts (a revoked credential, a budget it keeps itself):
|
|
140
|
+
await app.get(OpenCodeTurns).stopTurn(runId, 'budget');
|
|
141
|
+
// → 'aborted' | 'settling' | 'pending' | 'ignored' | 'unconfirmed'
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
| Option | Type | What it does |
|
|
145
|
+
| --- | --- | --- |
|
|
146
|
+
| `limits.steps` | `number` | Model rounds (`session.step.started`) the turn may run; the next one stops it. |
|
|
147
|
+
| `limits.compactions` | `number` | Compactions (`session.compaction.started`) the turn may run; the next one stops it. |
|
|
148
|
+
| `limits.wallClockMs` | `number` | Stops the turn this long after its `begin`, time parked on a person included. |
|
|
149
|
+
| `onTurnStart` | `({ turnId, threadId, sessionId, resumed }) => void \| Promise<void>` | This process starts executing the turn; `resumed: true` when it took the run over from another (a crash, a run resumed elsewhere after a person answered). |
|
|
150
|
+
| `onTurnEvent` | `({ turnId, type, sessionId }) => void` | One OpenCode event of the turn's own session: its type, never its payload. |
|
|
151
|
+
| `onStop` | `({ turnId, reason, counts }) => void \| Promise<void>` | A stop landed. `counts` is `{ steps, compactions, elapsedMs }`. |
|
|
152
|
+
| `onTurnEnd` | `({ turnId, status, stop? }) => void \| Promise<void>` | This process is done with the turn. `status` is `'succeeded' \| 'failed' \| 'interrupted' \| 'released'`. |
|
|
153
|
+
| `hookTimeoutMs` | `number` | How long a step waits for `onTurnStart`. Default 5 seconds; must be positive. |
|
|
154
|
+
|
|
155
|
+
Limits count what a turn may do: a limit of N allows N steps (or compactions), and the next one
|
|
156
|
+
stops the turn. `turnId` is the run id. Only OpenCode's events for the turn's own session count
|
|
157
|
+
(another thread's session or a subagent's do not).
|
|
158
|
+
|
|
159
|
+
**How a stop ends the turn.** A stop aborts the session (`session.interrupt`), so OpenCode starts no
|
|
160
|
+
new round. The turn ends `failed` with `outcome.stop = { reason, counts }`, which the host's
|
|
161
|
+
`beforeSettle` and `onSettled` see on the `OpenCodeRunResult`. The run row and the stream carry the
|
|
162
|
+
error code `opencode_stopped`, and the durable workflow returns `{ outcome: 'failed', stop: reason }`.
|
|
163
|
+
`reason` is `'steps' | 'compactions' | 'wall-clock'`, or what the app passed to `stopTurn`. Return
|
|
164
|
+
`{ error }` from `beforeSettle` to word the message the person reads. When the answer was already
|
|
165
|
+
settling as the stop landed, the answer is kept: the turn ends `succeeded`, with `stop` still set on
|
|
166
|
+
the outcome, `onTurnEnd` and the workflow result, and `onStop` still called.
|
|
167
|
+
|
|
168
|
+
**What a stop aborts.** A thread's session is reused by its next turn. So, immediately before the
|
|
169
|
+
abort, the engine checks that the turn is still running here, still owns its session, and is still
|
|
170
|
+
the run the thread's store holds. If any check fails, the stop is `'ignored'`. If the store cannot be
|
|
171
|
+
read, nothing is aborted and the stop is `'unconfirmed'` (ask again). A stop for a turn that ended
|
|
172
|
+
does nothing.
|
|
173
|
+
|
|
174
|
+
**A stop while the turn waits on a person** returns `'pending'`. The ask it waits on is refused,
|
|
175
|
+
whatever the person answers (an approval becomes a rejection, a form is skipped), so no action runs.
|
|
176
|
+
The stop then lands as soon as the turn observes OpenCode again. A pending stop on a turn that is
|
|
177
|
+
then cancelled while parked (released as `interrupted`, below) is dropped with it: no `onStop`
|
|
178
|
+
fires, and `onTurnEnd` carries no `stop`. If the OpenCode server restarted
|
|
179
|
+
while the turn waited, no new session is opened or prompted: the turn ends stopped. To end a parked run at once, cancel
|
|
180
|
+
it.
|
|
181
|
+
|
|
182
|
+
**Where the hooks run.** The hooks are called in the process that executes the turn's steps. With
|
|
183
|
+
`openCodeDurable()` any worker may claim a run, so that is the worker that runs them, not the one
|
|
184
|
+
that admitted the question. They can also be set on the host (both are called, the options' first).
|
|
185
|
+
Their errors are logged and never fail the turn. Only `onTurnStart` is waited for, at most
|
|
186
|
+
`hookTimeoutMs`; the others are called and not waited for.
|
|
187
|
+
|
|
188
|
+
**`onTurnEnd` is paired with `onTurnStart`.** Every process that gets `onTurnStart` for a turn gets
|
|
189
|
+
exactly one `onTurnEnd` for it, and its per-turn state goes with it:
|
|
190
|
+
|
|
191
|
+
- The turn settled there: `succeeded`, `failed` or `interrupted`.
|
|
192
|
+
- It was cancelled from another process while parked there: `interrupted`. The session's execution
|
|
193
|
+
was interrupted under the parked turn, so the store is read again: right away, then at about
|
|
194
|
+
0.25, 1.25, 4.25, 14.25 and 44.25 seconds (one read at a time, at most one every 250 ms). The turn
|
|
195
|
+
is let go when the store shows the ask settled, when the store cannot tell (no `toolCallApproval`,
|
|
196
|
+
a failed read, the ask not on file) after the first recheck, or when the rechecks run out.
|
|
197
|
+
- Another worker went on with it after a person answered: `released`. This fires on the next round
|
|
198
|
+
(`session.step.started`), or when an event of the session finds the parked ask decided in the
|
|
199
|
+
store by someone else.
|
|
200
|
+
- The process shut down while executing it: `released`.
|
|
201
|
+
|
|
202
|
+
While a turn is parked here, OpenCode may keep running the step's other tool calls. Their events
|
|
203
|
+
are streamed and recorded as usual, and they do not count as the turn moving elsewhere. If the
|
|
204
|
+
session's execution ends some other way (succeeded or failed) while the ask is still open, the turn
|
|
205
|
+
stays here for its answer, and a stop asked meanwhile returns `'pending'`.
|
|
206
|
+
|
|
207
|
+
A process that never executed the turn (the API that admitted or cancelled it) gets no hooks.
|
|
208
|
+
`stopTurn` acts only in the process executing the turn; anywhere else, and after `onTurnEnd`, it
|
|
209
|
+
returns `'ignored'`.
|
|
210
|
+
|
|
123
211
|
## What the session gets from the module
|
|
124
212
|
|
|
125
213
|
| Option | In the session |
|