@crouter/sdk 0.3.389 → 0.3.390
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 +2 -0
- package/dist/resources/runs.d.ts +8 -1
- package/dist/resources/runs.js +18 -6
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -97,6 +97,8 @@ const text = await client.runs.reply(runId, sent).text();
|
|
|
97
97
|
|
|
98
98
|
To answer a first message in the run's first turn, pass it to `runs.start`: `message` is stored with the run and always joins the first turn as context ahead of `prompt`, and the result's `message` is what `runs.reply` takes. (A `runs.message(id, text, {start_turn: false})` sent after `runs.start` joins the first turn only if it is stored before the run claims that turn's messages.)
|
|
99
99
|
|
|
100
|
+
`runs.message(id, text, {idempotencyKey})` sends an `Idempotency-Key` (a fresh `crypto.randomUUID()` per call, reused across the SDK's own retries). The daemon delivers a key once for 24 hours and returns the original result on replay; the same key with a different body fails with `409 idempotency_conflict`. Pass your own key, derived from your message id, to dedupe across process restarts.
|
|
101
|
+
|
|
100
102
|
```ts
|
|
101
103
|
const run = await client.runs.start({prompt: instructions, message: 'Hello'});
|
|
102
104
|
const {text, ended, error} = await client.runs.reply(run.run_id, run.message!).collect({onDelta: (delta) => send(delta)});
|
package/dist/resources/runs.d.ts
CHANGED
|
@@ -138,9 +138,16 @@ export declare class Runs {
|
|
|
138
138
|
* `start_turn: false` stores it without starting a turn; it is delivered as
|
|
139
139
|
* context at the start of the next turn, ahead of that turn's input. For a
|
|
140
140
|
* new run that is the first turn only if it is stored before the run claims
|
|
141
|
-
* its first turn's messages; to guarantee it, pass `message` to `runs.start`.
|
|
141
|
+
* its first turn's messages; to guarantee it, pass `message` to `runs.start`.
|
|
142
|
+
*
|
|
143
|
+
* The send carries an `Idempotency-Key`: `idempotencyKey` when given, otherwise a UUID generated for this
|
|
144
|
+
* call and reused on every retry of it. The daemon keeps the key for 24 hours; a repeat with the same key
|
|
145
|
+
* and the same message returns the first send's result without delivering again, and the same key with a
|
|
146
|
+
* different message or `start_turn` throws `ConflictError` `idempotency_conflict`. To make a send safe
|
|
147
|
+
* across your own restarts, derive the key from your own record of the send (a batch or row id). */
|
|
142
148
|
message(runId: string, message: string, options?: RequestOptions & {
|
|
143
149
|
start_turn?: boolean;
|
|
150
|
+
idempotencyKey?: string;
|
|
144
151
|
}): Promise<SentRunMessage>;
|
|
145
152
|
interrupt(runId: string, options?: RequestOptions & {
|
|
146
153
|
children?: 'stop' | 'continue';
|
package/dist/resources/runs.js
CHANGED
|
@@ -19,8 +19,7 @@ export class Runs {
|
|
|
19
19
|
return await this.request('POST', '/v1/runs', body, { ...rest, maxRetries: 0, headers: { ...rest.headers, 'Idempotency-Key': idempotencyKey } });
|
|
20
20
|
}
|
|
21
21
|
catch (error) {
|
|
22
|
-
if (attempt >= maxRetries || !(error instanceof APIError) ||
|
|
23
|
-
!(error.retryable === true || error.code === 'transport_error' || error.code === 'daemon_request_interrupted' || error.code === 'connection_error' || error.code === 'request_timeout'))
|
|
22
|
+
if (attempt >= maxRetries || !(error instanceof APIError) || !retryableSend(error))
|
|
24
23
|
throw error;
|
|
25
24
|
await pause(error.retryAfterS ? error.retryAfterS * 1_000 : 500 * 2 ** attempt, rest.signal);
|
|
26
25
|
}
|
|
@@ -53,15 +52,24 @@ export class Runs {
|
|
|
53
52
|
* `start_turn: false` stores it without starting a turn; it is delivered as
|
|
54
53
|
* context at the start of the next turn, ahead of that turn's input. For a
|
|
55
54
|
* new run that is the first turn only if it is stored before the run claims
|
|
56
|
-
* its first turn's messages; to guarantee it, pass `message` to `runs.start`.
|
|
55
|
+
* its first turn's messages; to guarantee it, pass `message` to `runs.start`.
|
|
56
|
+
*
|
|
57
|
+
* The send carries an `Idempotency-Key`: `idempotencyKey` when given, otherwise a UUID generated for this
|
|
58
|
+
* call and reused on every retry of it. The daemon keeps the key for 24 hours; a repeat with the same key
|
|
59
|
+
* and the same message returns the first send's result without delivering again, and the same key with a
|
|
60
|
+
* different message or `start_turn` throws `ConflictError` `idempotency_conflict`. To make a send safe
|
|
61
|
+
* across your own restarts, derive the key from your own record of the send (a batch or row id). */
|
|
57
62
|
async message(runId, message, options = {}) {
|
|
58
|
-
const { maxRetries = 2, start_turn, ...rest } = options;
|
|
63
|
+
const { idempotencyKey = crypto.randomUUID(), maxRetries = 2, start_turn, ...rest } = options;
|
|
64
|
+
const body = { message, ...(start_turn === undefined ? {} : { start_turn }) };
|
|
65
|
+
// One key per logical send, reused on every retry: the daemon answers a repeat with the first send's
|
|
66
|
+
// result, so a retry after a lost response cannot deliver the message twice.
|
|
59
67
|
for (let attempt = 0;; attempt++) {
|
|
60
68
|
try {
|
|
61
|
-
return await this.request('POST', `${runPath(runId)}/messages`, {
|
|
69
|
+
return await this.request('POST', `${runPath(runId)}/messages`, body, { ...rest, maxRetries: 0, headers: { ...rest.headers, 'Idempotency-Key': idempotencyKey } });
|
|
62
70
|
}
|
|
63
71
|
catch (error) {
|
|
64
|
-
if (attempt >= maxRetries || !(error instanceof APIError) || error
|
|
72
|
+
if (attempt >= maxRetries || !(error instanceof APIError) || !retryableSend(error))
|
|
65
73
|
throw error;
|
|
66
74
|
await pause(error.retryAfterS ? error.retryAfterS * 1_000 : 500 * 2 ** attempt, rest.signal);
|
|
67
75
|
}
|
|
@@ -162,6 +170,10 @@ export class Runs {
|
|
|
162
170
|
}
|
|
163
171
|
}
|
|
164
172
|
}
|
|
173
|
+
/** A keyed send is safe to repeat after a refusal the daemon marked retryable or a transport failure. */
|
|
174
|
+
function retryableSend(error) {
|
|
175
|
+
return error.retryable === true || error.code === 'transport_error' || error.code === 'daemon_request_interrupted' || error.code === 'connection_error' || error.code === 'request_timeout';
|
|
176
|
+
}
|
|
165
177
|
function runPath(id) {
|
|
166
178
|
if (!/^[A-Za-z0-9_-]+$/.test(id))
|
|
167
179
|
throw new TypeError('Invalid run id');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crouter/sdk",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.390",
|
|
4
4
|
"description": "Typed Node and browser client for running crouter agents through the crtrd /v1 API.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"build": "tsc -p tsconfig.json && chmod 755 dist/keygen-cli.js"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@crouter/api": "^0.3.
|
|
35
|
-
"@crouter/identity": "^0.3.
|
|
34
|
+
"@crouter/api": "^0.3.390",
|
|
35
|
+
"@crouter/identity": "^0.3.390",
|
|
36
36
|
"jose": "^6.2.1"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|