@pikku/core 0.12.136 → 0.12.138
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 +41 -0
- package/dist/dev/hot-reload.js +5 -2
- package/dist/env.d.ts +18 -0
- package/dist/env.js +60 -1
- package/dist/function/abort-scope.d.ts +6 -5
- package/dist/function/abort-scope.js +6 -17
- package/dist/function/function-runner.js +2 -1
- package/dist/middleware/auth-apikey.js +1 -1
- package/dist/middleware/auth-bearer.js +1 -1
- package/dist/middleware/auth-cookie.js +1 -1
- package/dist/schema.js +3 -1
- package/dist/services/in-memory-queue-service.js +4 -2
- package/dist/services/in-memory-workflow-service.js +7 -6
- package/dist/services/index.d.ts +0 -1
- package/dist/services/index.js +0 -1
- package/dist/services/local-variables.d.ts +5 -0
- package/dist/services/local-variables.js +7 -2
- package/dist/services/meta-service.d.ts +11 -0
- package/dist/services/meta-service.js +10 -1
- package/dist/services/queue-webhook-service.js +1 -1
- package/dist/services/webhook-service.d.ts +2 -2
- package/dist/services/webhook-service.js +6 -4
- package/dist/types/core.types.d.ts +10 -1
- package/dist/utils/hmac.d.ts +25 -10
- package/dist/utils/hmac.js +202 -24
- package/dist/wirings/agent/agent-agui.js +65 -62
- package/dist/wirings/agent/agent-interrupt.d.ts +4 -1
- package/dist/wirings/agent/agent-interrupt.js +3 -3
- package/dist/wirings/agent/agent-memory.js +5 -6
- package/dist/wirings/agent/agent-model-config.js +2 -1
- package/dist/wirings/agent/agent-prepare.js +21 -13
- package/dist/wirings/agent/agent-stream.js +38 -34
- package/dist/wirings/agent/agent.types.d.ts +8 -1
- package/dist/wirings/channel/channel-handler.js +1 -1
- package/dist/wirings/channel/local/local-channel-handler.js +5 -1
- package/dist/wirings/channel/local/local-channel-runner.js +6 -4
- package/dist/wirings/channel/local/local-eventhub-service.d.ts +1 -0
- package/dist/wirings/channel/local/local-eventhub-service.js +4 -1
- package/dist/wirings/cli/channel/cli-channel-runner.js +7 -2
- package/dist/wirings/cli/channel/cli-raw-client-runner.js +8 -4
- package/dist/wirings/cli/cli-runner.js +1 -1
- package/dist/wirings/gateway/gateway-runner.js +3 -3
- package/dist/wirings/http/http-runner.js +6 -6
- package/dist/wirings/persona/persona-app-scopes.js +1 -0
- package/dist/wirings/scheduler/scheduler-runner.js +1 -1
- package/dist/wirings/scope/define-scope.d.ts +19 -0
- package/dist/wirings/scope/define-scope.js +19 -0
- package/dist/wirings/scope/index.d.ts +2 -2
- package/dist/wirings/scope/index.js +1 -1
- package/dist/wirings/scope/scope.types.d.ts +14 -0
- package/dist/wirings/scope/validate-scope-definitions.js +1 -0
- package/dist/wirings/trigger/trigger-runner.js +3 -1
- package/dist/wirings/trigger/webhook-source-runner.js +5 -2
- package/dist/wirings/virtual-user/virtual-user-scaffold.js +3 -2
- package/dist/wirings/workflow/scenario-coverage.d.ts +70 -0
- package/dist/wirings/workflow/scenario-coverage.js +159 -0
- package/dist/wirings/workflow/workflow-invocation-id.js +85 -8
- package/package.json +4 -1
- package/src/public-surface.json +10 -3
package/dist/utils/hmac.js
CHANGED
|
@@ -1,42 +1,220 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Web Crypto only: no `node:crypto` and no `Buffer`, so this loads on edge
|
|
3
|
+
* runtimes. Web Crypto is async, so everything that touches a key returns a
|
|
4
|
+
* Promise; `timingSafeStringEqual` is plain JS and stays synchronous.
|
|
5
|
+
*/
|
|
6
|
+
const encoder = new TextEncoder();
|
|
7
|
+
const subtle = () => {
|
|
8
|
+
const s = globalThis.crypto?.subtle;
|
|
9
|
+
if (!s)
|
|
10
|
+
throw new Error('WebCrypto not available');
|
|
11
|
+
return s;
|
|
12
|
+
};
|
|
13
|
+
const bytesToHex = (bytes) => {
|
|
14
|
+
let out = '';
|
|
15
|
+
for (const b of bytes)
|
|
16
|
+
out += b.toString(16).padStart(2, '0');
|
|
17
|
+
return out;
|
|
18
|
+
};
|
|
19
|
+
const bytesToBase64 = (bytes) => {
|
|
20
|
+
let binary = '';
|
|
21
|
+
for (const b of bytes)
|
|
22
|
+
binary += String.fromCharCode(b);
|
|
23
|
+
return btoa(binary);
|
|
24
|
+
};
|
|
25
|
+
/** Lenient like Node's `Buffer.from(_, 'hex')`: stops at the first bad pair. */
|
|
26
|
+
const hexToBytes = (hex) => {
|
|
27
|
+
const out = [];
|
|
28
|
+
for (let i = 0; i + 1 < hex.length; i += 2) {
|
|
29
|
+
const pair = hex.slice(i, i + 2);
|
|
30
|
+
if (!/^[0-9a-fA-F]{2}$/.test(pair))
|
|
31
|
+
break;
|
|
32
|
+
out.push(parseInt(pair, 16));
|
|
33
|
+
}
|
|
34
|
+
return Uint8Array.from(out);
|
|
35
|
+
};
|
|
36
|
+
/** Lenient like Node's `Buffer.from(_, 'base64')`: accepts base64url, ignores junk. */
|
|
37
|
+
const base64ToBytes = (value) => {
|
|
38
|
+
let clean = value
|
|
39
|
+
.replace(/-/g, '+')
|
|
40
|
+
.replace(/_/g, '/')
|
|
41
|
+
.replace(/[^A-Za-z0-9+/]/g, '');
|
|
42
|
+
if (clean.length % 4 === 1)
|
|
43
|
+
clean = clean.slice(0, -1);
|
|
44
|
+
clean += '='.repeat((4 - (clean.length % 4)) % 4);
|
|
45
|
+
const binary = atob(clean);
|
|
46
|
+
const out = new Uint8Array(binary.length);
|
|
47
|
+
for (let i = 0; i < binary.length; i++)
|
|
48
|
+
out[i] = binary.charCodeAt(i);
|
|
49
|
+
return out;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Constant-time string comparison (pure JS; for opaque tokens, not HMACs, which
|
|
53
|
+
* `verifyHmacSignature` checks with Web Crypto). Returns false on a length mismatch
|
|
54
|
+
* (the lengths of the two values are not secret).
|
|
55
|
+
*/
|
|
56
|
+
export function timingSafeStringEqual(a, b) {
|
|
57
|
+
const left = encoder.encode(a);
|
|
58
|
+
const right = encoder.encode(b);
|
|
59
|
+
if (left.length !== right.length)
|
|
60
|
+
return false;
|
|
61
|
+
let diff = 0;
|
|
62
|
+
for (let i = 0; i < left.length; i++)
|
|
63
|
+
diff |= left[i] ^ right[i];
|
|
64
|
+
return diff === 0;
|
|
65
|
+
}
|
|
66
|
+
const HMAC_HASH = {
|
|
67
|
+
sha1: 'SHA-1',
|
|
68
|
+
sha256: 'SHA-256',
|
|
69
|
+
sha512: 'SHA-512',
|
|
70
|
+
};
|
|
71
|
+
const rawSecretBytes = (secret, encoding) => encoding === 'hex'
|
|
72
|
+
? hexToBytes(secret)
|
|
73
|
+
: encoding === 'base64'
|
|
74
|
+
? base64ToBytes(secret)
|
|
75
|
+
: encoder.encode(secret);
|
|
76
|
+
const secretBytes = (secret, encoding) => {
|
|
77
|
+
const bytes = rawSecretBytes(secret, encoding);
|
|
78
|
+
// Web Crypto refuses a zero-length key. HMAC zero-pads the key to the block
|
|
79
|
+
// size, so a single zero byte is the same key as an empty one. Only signing
|
|
80
|
+
// reaches here: verification refuses an empty key outright.
|
|
81
|
+
return bytes.length ? bytes : new Uint8Array(1);
|
|
82
|
+
};
|
|
83
|
+
const hmacBytes = async (secret, algorithm, payload, secretEncoding = 'utf8') => {
|
|
84
|
+
const hash = HMAC_HASH[algorithm];
|
|
85
|
+
if (!hash)
|
|
86
|
+
throw new Error(`Unsupported HMAC algorithm '${algorithm}'`);
|
|
87
|
+
const key = await subtle().importKey('raw', secretBytes(secret, secretEncoding), { name: 'HMAC', hash }, false, ['sign']);
|
|
88
|
+
const data = typeof payload === 'string' ? encoder.encode(payload) : payload;
|
|
89
|
+
return new Uint8Array(await subtle().sign('HMAC', key, data));
|
|
90
|
+
};
|
|
2
91
|
/**
|
|
3
92
|
* HMAC-SHA256 of a payload, hex-encoded. Senders wrap this in their own scheme
|
|
4
93
|
* prefix (`sha256=`, `v0=`, …).
|
|
5
94
|
*/
|
|
6
|
-
export function hmacSha256Hex(secret, payload) {
|
|
7
|
-
return
|
|
95
|
+
export async function hmacSha256Hex(secret, payload) {
|
|
96
|
+
return bytesToHex(await hmacBytes(secret, 'sha256', payload));
|
|
97
|
+
}
|
|
98
|
+
/** The HMAC of `payload` under `secret`, for handshakes that answer with a digest. */
|
|
99
|
+
export async function hmacDigest(secret, algorithm, payload, encoding, secretEncoding = 'utf8') {
|
|
100
|
+
const digest = await hmacBytes(secret, algorithm, payload, secretEncoding);
|
|
101
|
+
return encoding === 'hex' ? bytesToHex(digest) : bytesToBase64(digest);
|
|
8
102
|
}
|
|
9
103
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
104
|
+
* Whether `signature` is the HMAC of `payload` under `secret`. Fails closed on
|
|
105
|
+
* an empty secret (or one that decodes to an empty key) and on a missing or
|
|
106
|
+
* malformed signature. The comparison is `crypto.subtle.verify('HMAC', ...)`,
|
|
107
|
+
* so it runs in the platform's constant-time implementation.
|
|
12
108
|
*/
|
|
13
|
-
export function
|
|
109
|
+
export async function verifyHmacSignature(secret, signature, algorithm, payload, encoding, secretEncoding = 'utf8') {
|
|
110
|
+
if (!secret || !signature)
|
|
111
|
+
return false;
|
|
14
112
|
try {
|
|
15
|
-
|
|
113
|
+
const hash = HMAC_HASH[algorithm];
|
|
114
|
+
if (!hash)
|
|
115
|
+
return false;
|
|
116
|
+
const keyBytes = rawSecretBytes(secret, secretEncoding);
|
|
117
|
+
if (!keyBytes.length)
|
|
118
|
+
return false;
|
|
119
|
+
if (encoding === 'hex' && !/^(?:[0-9a-fA-F]{2})+$/.test(signature)) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
const sig = encoding === 'hex' ? hexToBytes(signature) : base64ToBytes(signature);
|
|
123
|
+
if (!sig.length)
|
|
124
|
+
return false;
|
|
125
|
+
const key = await subtle().importKey('raw', keyBytes, { name: 'HMAC', hash }, false, ['verify']);
|
|
126
|
+
const data = typeof payload === 'string' ? encoder.encode(payload) : payload;
|
|
127
|
+
return await subtle().verify('HMAC', key, sig, data);
|
|
16
128
|
}
|
|
17
129
|
catch {
|
|
18
130
|
return false;
|
|
19
131
|
}
|
|
20
132
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
133
|
+
// DER prefixes of the SPKI algorithm identifiers we can import.
|
|
134
|
+
const OID_RSA = '06092a864886f70d010101';
|
|
135
|
+
const OID_EC = '06072a8648ce3d0201';
|
|
136
|
+
const OID_ED25519 = '06032b6570';
|
|
137
|
+
const EC_CURVES = {
|
|
138
|
+
'06082a8648ce3d030107': { name: 'P-256', size: 32 },
|
|
139
|
+
'06052b81040022': { name: 'P-384', size: 48 },
|
|
140
|
+
'06052b81040023': { name: 'P-521', size: 66 },
|
|
141
|
+
};
|
|
142
|
+
const SIGNATURE_HASH = {
|
|
143
|
+
sha1: 'SHA-1',
|
|
144
|
+
sha256: 'SHA-256',
|
|
145
|
+
sha384: 'SHA-384',
|
|
146
|
+
sha512: 'SHA-512',
|
|
147
|
+
};
|
|
148
|
+
const pemToDer = (pem) => base64ToBytes(pem.replace(/-----[A-Z ]+-----/g, '').replace(/\s+/g, ''));
|
|
149
|
+
/** Reads an ASN.1 DER length at `offset`; returns [length, bytesUsed]. */
|
|
150
|
+
const readLength = (der, offset) => {
|
|
151
|
+
const first = der[offset];
|
|
152
|
+
if (first < 0x80)
|
|
153
|
+
return [first, 1];
|
|
154
|
+
const count = first & 0x7f;
|
|
155
|
+
let length = 0;
|
|
156
|
+
for (let i = 0; i < count; i++)
|
|
157
|
+
length = (length << 8) | der[offset + 1 + i];
|
|
158
|
+
return [length, 1 + count];
|
|
159
|
+
};
|
|
160
|
+
/** An ECDSA signature as DER `SEQUENCE { r, s }` to the fixed-width `r || s` Web Crypto expects. */
|
|
161
|
+
const derToP1363 = (der, size) => {
|
|
162
|
+
if (der[0] !== 0x30)
|
|
163
|
+
throw new Error('Invalid DER signature');
|
|
164
|
+
let offset = 1 + readLength(der, 1)[1];
|
|
165
|
+
const out = new Uint8Array(size * 2);
|
|
166
|
+
for (let part = 0; part < 2; part++) {
|
|
167
|
+
if (der[offset] !== 0x02)
|
|
168
|
+
throw new Error('Invalid DER signature');
|
|
169
|
+
const [length, used] = readLength(der, offset + 1);
|
|
170
|
+
let integer = der.slice(offset + 1 + used, offset + 1 + used + length);
|
|
171
|
+
while (integer.length > size && integer[0] === 0)
|
|
172
|
+
integer = integer.slice(1);
|
|
173
|
+
if (integer.length > size)
|
|
174
|
+
throw new Error('Invalid DER signature');
|
|
175
|
+
out.set(integer, part * size + (size - integer.length));
|
|
176
|
+
offset += 1 + used + length;
|
|
177
|
+
}
|
|
178
|
+
return out;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Whether `signature` (base64) is `payload` signed by the private half of
|
|
182
|
+
* `publicKey` (an RSA, EC or Ed25519 PEM `SPKI` key). RSA-PSS keys are not
|
|
183
|
+
* supported and verify as false. Ed25519 ignores `algorithm` and `dsaEncoding`.
|
|
184
|
+
*/
|
|
185
|
+
export async function verifyPublicKeySignature(publicKey, signature, payload, options = {}) {
|
|
186
|
+
if (!publicKey || !signature)
|
|
35
187
|
return false;
|
|
36
188
|
try {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
189
|
+
const der0 = pemToDer(publicKey);
|
|
190
|
+
if (bytesToHex(der0).includes(OID_ED25519)) {
|
|
191
|
+
const key = await subtle().importKey('spki', der0, { name: 'Ed25519' }, false, ['verify']);
|
|
192
|
+
return await subtle().verify({ name: 'Ed25519' }, key, base64ToBytes(signature), (typeof payload === 'string'
|
|
193
|
+
? encoder.encode(payload)
|
|
194
|
+
: payload));
|
|
195
|
+
}
|
|
196
|
+
const hash = SIGNATURE_HASH[(options.algorithm ?? 'sha256')
|
|
197
|
+
.toLowerCase()
|
|
198
|
+
.replace(/^rsa-/, '')
|
|
199
|
+
.replace(/-/g, '')];
|
|
200
|
+
if (!hash)
|
|
201
|
+
return false;
|
|
202
|
+
const der = pemToDer(publicKey);
|
|
203
|
+
const hex = bytesToHex(der);
|
|
204
|
+
const data = typeof payload === 'string' ? encoder.encode(payload) : payload;
|
|
205
|
+
const sig = base64ToBytes(signature);
|
|
206
|
+
if (hex.includes(OID_RSA)) {
|
|
207
|
+
const key = await subtle().importKey('spki', der, { name: 'RSASSA-PKCS1-v1_5', hash }, false, ['verify']);
|
|
208
|
+
return await subtle().verify('RSASSA-PKCS1-v1_5', key, sig, data);
|
|
209
|
+
}
|
|
210
|
+
const curve = hex.includes(OID_EC)
|
|
211
|
+
? Object.entries(EC_CURVES).find(([oid]) => hex.includes(oid))?.[1]
|
|
212
|
+
: undefined;
|
|
213
|
+
if (!curve)
|
|
214
|
+
return false;
|
|
215
|
+
const key = await subtle().importKey('spki', der, { name: 'ECDSA', namedCurve: curve.name }, false, ['verify']);
|
|
216
|
+
const raw = options.dsaEncoding === 'ieee-p1363' ? sig : derToP1363(sig, curve.size);
|
|
217
|
+
return await subtle().verify({ name: 'ECDSA', hash }, key, raw, data);
|
|
40
218
|
}
|
|
41
219
|
catch {
|
|
42
220
|
return false;
|
|
@@ -27,59 +27,62 @@ export function wrapChannelWithAGUI(inner, options) {
|
|
|
27
27
|
let sawUsage = false;
|
|
28
28
|
let usageModel;
|
|
29
29
|
const usageTotals = { input: 0, output: 0 };
|
|
30
|
-
function send(event) {
|
|
30
|
+
async function send(event) {
|
|
31
31
|
if (!runStartedSent) {
|
|
32
32
|
runStartedSent = true;
|
|
33
|
-
inner.send({
|
|
33
|
+
await inner.send({
|
|
34
34
|
type: 'RUN_STARTED',
|
|
35
35
|
threadId,
|
|
36
36
|
runId: resolveRunId(),
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
|
-
inner.send(event);
|
|
39
|
+
await inner.send(event);
|
|
40
40
|
}
|
|
41
|
-
function endTextMessage() {
|
|
41
|
+
async function endTextMessage() {
|
|
42
42
|
if (textMessageId) {
|
|
43
|
-
send({ type: 'TEXT_MESSAGE_END', messageId: textMessageId });
|
|
43
|
+
await send({ type: 'TEXT_MESSAGE_END', messageId: textMessageId });
|
|
44
44
|
textMessageId = null;
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
-
function endThinkingMessage() {
|
|
47
|
+
async function endThinkingMessage() {
|
|
48
48
|
if (thinkingMessageId) {
|
|
49
|
-
send({
|
|
50
|
-
|
|
49
|
+
await send({
|
|
50
|
+
type: 'THINKING_TEXT_MESSAGE_END',
|
|
51
|
+
messageId: thinkingMessageId,
|
|
52
|
+
});
|
|
53
|
+
await send({ type: 'THINKING_END' });
|
|
51
54
|
thinkingMessageId = null;
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
|
-
function endStep() {
|
|
57
|
+
async function endStep() {
|
|
55
58
|
if (openStepName) {
|
|
56
|
-
send({ type: 'STEP_FINISHED', stepName: openStepName });
|
|
59
|
+
await send({ type: 'STEP_FINISHED', stepName: openStepName });
|
|
57
60
|
openStepName = null;
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
|
-
function ensureTextMessage() {
|
|
63
|
+
async function ensureTextMessage() {
|
|
61
64
|
if (!textMessageId) {
|
|
62
65
|
textMessageId = randomUUID();
|
|
63
|
-
send({ type: 'TEXT_MESSAGE_START', messageId: textMessageId });
|
|
66
|
+
await send({ type: 'TEXT_MESSAGE_START', messageId: textMessageId });
|
|
64
67
|
}
|
|
65
68
|
return textMessageId;
|
|
66
69
|
}
|
|
67
|
-
function ensureThinkingMessage() {
|
|
70
|
+
async function ensureThinkingMessage() {
|
|
68
71
|
if (!thinkingMessageId) {
|
|
69
72
|
thinkingMessageId = randomUUID();
|
|
70
|
-
send({ type: 'THINKING_START' });
|
|
71
|
-
send({
|
|
73
|
+
await send({ type: 'THINKING_START' });
|
|
74
|
+
await send({
|
|
72
75
|
type: 'THINKING_TEXT_MESSAGE_START',
|
|
73
76
|
messageId: thinkingMessageId,
|
|
74
77
|
});
|
|
75
78
|
}
|
|
76
79
|
return thinkingMessageId;
|
|
77
80
|
}
|
|
78
|
-
function finishRun() {
|
|
79
|
-
endTextMessage();
|
|
80
|
-
endThinkingMessage();
|
|
81
|
-
endStep();
|
|
82
|
-
send({
|
|
81
|
+
async function finishRun() {
|
|
82
|
+
await endTextMessage();
|
|
83
|
+
await endThinkingMessage();
|
|
84
|
+
await endStep();
|
|
85
|
+
await send({
|
|
83
86
|
type: 'RUN_FINISHED',
|
|
84
87
|
threadId,
|
|
85
88
|
runId: resolveRunId(),
|
|
@@ -110,14 +113,14 @@ export function wrapChannelWithAGUI(inner, options) {
|
|
|
110
113
|
remote: (funcName, data) => inner.remote(funcName, data),
|
|
111
114
|
sendBinary: (data) => inner.sendBinary(data),
|
|
112
115
|
close: () => inner.close(),
|
|
113
|
-
send: (event) => {
|
|
116
|
+
send: async (event) => {
|
|
114
117
|
if (terminal)
|
|
115
118
|
return;
|
|
116
119
|
switch (event.type) {
|
|
117
120
|
case 'text-delta': {
|
|
118
|
-
endThinkingMessage();
|
|
119
|
-
const id = ensureTextMessage();
|
|
120
|
-
send({
|
|
121
|
+
await endThinkingMessage();
|
|
122
|
+
const id = await ensureTextMessage();
|
|
123
|
+
await send({
|
|
121
124
|
type: 'TEXT_MESSAGE_CONTENT',
|
|
122
125
|
messageId: id,
|
|
123
126
|
delta: event.text,
|
|
@@ -125,9 +128,9 @@ export function wrapChannelWithAGUI(inner, options) {
|
|
|
125
128
|
break;
|
|
126
129
|
}
|
|
127
130
|
case 'reasoning-delta': {
|
|
128
|
-
endTextMessage();
|
|
129
|
-
const id = ensureThinkingMessage();
|
|
130
|
-
send({
|
|
131
|
+
await endTextMessage();
|
|
132
|
+
const id = await ensureThinkingMessage();
|
|
133
|
+
await send({
|
|
131
134
|
type: 'THINKING_TEXT_MESSAGE_CONTENT',
|
|
132
135
|
messageId: id,
|
|
133
136
|
delta: event.text,
|
|
@@ -135,19 +138,19 @@ export function wrapChannelWithAGUI(inner, options) {
|
|
|
135
138
|
break;
|
|
136
139
|
}
|
|
137
140
|
case 'tool-call': {
|
|
138
|
-
endTextMessage();
|
|
139
|
-
endThinkingMessage();
|
|
140
|
-
send({
|
|
141
|
+
await endTextMessage();
|
|
142
|
+
await endThinkingMessage();
|
|
143
|
+
await send({
|
|
141
144
|
type: 'TOOL_CALL_START',
|
|
142
145
|
toolCallId: event.toolCallId,
|
|
143
146
|
toolCallName: event.toolName,
|
|
144
147
|
});
|
|
145
|
-
send({
|
|
148
|
+
await send({
|
|
146
149
|
type: 'TOOL_CALL_ARGS',
|
|
147
150
|
toolCallId: event.toolCallId,
|
|
148
151
|
delta: resultToString(event.args) || '{}',
|
|
149
152
|
});
|
|
150
|
-
send({
|
|
153
|
+
await send({
|
|
151
154
|
type: 'TOOL_CALL_END',
|
|
152
155
|
toolCallId: event.toolCallId,
|
|
153
156
|
toolCallName: event.toolName,
|
|
@@ -155,7 +158,7 @@ export function wrapChannelWithAGUI(inner, options) {
|
|
|
155
158
|
break;
|
|
156
159
|
}
|
|
157
160
|
case 'tool-result': {
|
|
158
|
-
send({
|
|
161
|
+
await send({
|
|
159
162
|
type: 'TOOL_CALL_RESULT',
|
|
160
163
|
messageId: randomUUID(),
|
|
161
164
|
toolCallId: event.toolCallId,
|
|
@@ -165,8 +168,8 @@ export function wrapChannelWithAGUI(inner, options) {
|
|
|
165
168
|
break;
|
|
166
169
|
}
|
|
167
170
|
case 'usage': {
|
|
168
|
-
endTextMessage();
|
|
169
|
-
endThinkingMessage();
|
|
171
|
+
await endTextMessage();
|
|
172
|
+
await endThinkingMessage();
|
|
170
173
|
sawUsage = true;
|
|
171
174
|
usageTotals.input += event.tokens.input;
|
|
172
175
|
usageTotals.output += event.tokens.output;
|
|
@@ -175,30 +178,30 @@ export function wrapChannelWithAGUI(inner, options) {
|
|
|
175
178
|
break;
|
|
176
179
|
}
|
|
177
180
|
case 'error': {
|
|
178
|
-
endTextMessage();
|
|
179
|
-
endThinkingMessage();
|
|
180
|
-
endStep();
|
|
181
|
-
send({ type: 'RUN_ERROR', message: event.message });
|
|
181
|
+
await endTextMessage();
|
|
182
|
+
await endThinkingMessage();
|
|
183
|
+
await endStep();
|
|
184
|
+
await send({ type: 'RUN_ERROR', message: event.message });
|
|
182
185
|
terminal = true;
|
|
183
186
|
break;
|
|
184
187
|
}
|
|
185
188
|
case 'done': {
|
|
186
|
-
finishRun();
|
|
189
|
+
await finishRun();
|
|
187
190
|
break;
|
|
188
191
|
}
|
|
189
192
|
case 'step-start': {
|
|
190
|
-
endTextMessage();
|
|
191
|
-
endThinkingMessage();
|
|
192
|
-
endStep();
|
|
193
|
+
await endTextMessage();
|
|
194
|
+
await endThinkingMessage();
|
|
195
|
+
await endStep();
|
|
193
196
|
stepSeq += 1;
|
|
194
197
|
openStepName = `${event.agent ?? 'step'}#${stepSeq}`;
|
|
195
|
-
send({ type: 'STEP_STARTED', stepName: openStepName });
|
|
198
|
+
await send({ type: 'STEP_STARTED', stepName: openStepName });
|
|
196
199
|
break;
|
|
197
200
|
}
|
|
198
201
|
case 'approval-request': {
|
|
199
|
-
endTextMessage();
|
|
200
|
-
endThinkingMessage();
|
|
201
|
-
send({
|
|
202
|
+
await endTextMessage();
|
|
203
|
+
await endThinkingMessage();
|
|
204
|
+
await send({
|
|
202
205
|
type: 'CUSTOM',
|
|
203
206
|
name: 'pikku:approval-request',
|
|
204
207
|
value: {
|
|
@@ -214,9 +217,9 @@ export function wrapChannelWithAGUI(inner, options) {
|
|
|
214
217
|
break;
|
|
215
218
|
}
|
|
216
219
|
case 'credential-request': {
|
|
217
|
-
endTextMessage();
|
|
218
|
-
endThinkingMessage();
|
|
219
|
-
send({
|
|
220
|
+
await endTextMessage();
|
|
221
|
+
await endThinkingMessage();
|
|
222
|
+
await send({
|
|
220
223
|
type: 'CUSTOM',
|
|
221
224
|
name: 'pikku:credential-request',
|
|
222
225
|
value: {
|
|
@@ -234,7 +237,7 @@ export function wrapChannelWithAGUI(inner, options) {
|
|
|
234
237
|
break;
|
|
235
238
|
}
|
|
236
239
|
case 'generative-ui': {
|
|
237
|
-
send({
|
|
240
|
+
await send({
|
|
238
241
|
type: 'CUSTOM',
|
|
239
242
|
name: 'pikku:generative-ui',
|
|
240
243
|
value: { spec: event.spec },
|
|
@@ -242,7 +245,7 @@ export function wrapChannelWithAGUI(inner, options) {
|
|
|
242
245
|
break;
|
|
243
246
|
}
|
|
244
247
|
case 'data': {
|
|
245
|
-
send({
|
|
248
|
+
await send({
|
|
246
249
|
type: 'CUSTOM',
|
|
247
250
|
name: 'pikku:data',
|
|
248
251
|
value: { name: event.name, data: event.data },
|
|
@@ -250,7 +253,7 @@ export function wrapChannelWithAGUI(inner, options) {
|
|
|
250
253
|
break;
|
|
251
254
|
}
|
|
252
255
|
case 'agent-call': {
|
|
253
|
-
send({
|
|
256
|
+
await send({
|
|
254
257
|
type: 'CUSTOM',
|
|
255
258
|
name: 'pikku:agent-call',
|
|
256
259
|
value: {
|
|
@@ -262,7 +265,7 @@ export function wrapChannelWithAGUI(inner, options) {
|
|
|
262
265
|
break;
|
|
263
266
|
}
|
|
264
267
|
case 'agent-result': {
|
|
265
|
-
send({
|
|
268
|
+
await send({
|
|
266
269
|
type: 'CUSTOM',
|
|
267
270
|
name: 'pikku:agent-result',
|
|
268
271
|
value: {
|
|
@@ -274,7 +277,7 @@ export function wrapChannelWithAGUI(inner, options) {
|
|
|
274
277
|
break;
|
|
275
278
|
}
|
|
276
279
|
case 'suspended': {
|
|
277
|
-
send({
|
|
280
|
+
await send({
|
|
278
281
|
type: 'CUSTOM',
|
|
279
282
|
name: 'pikku:suspended',
|
|
280
283
|
value: { reason: event.reason, missingRpcs: event.missingRpcs },
|
|
@@ -282,10 +285,10 @@ export function wrapChannelWithAGUI(inner, options) {
|
|
|
282
285
|
break;
|
|
283
286
|
}
|
|
284
287
|
case 'interrupted': {
|
|
285
|
-
endTextMessage();
|
|
286
|
-
endThinkingMessage();
|
|
287
|
-
endStep();
|
|
288
|
-
send({
|
|
288
|
+
await endTextMessage();
|
|
289
|
+
await endThinkingMessage();
|
|
290
|
+
await endStep();
|
|
291
|
+
await send({
|
|
289
292
|
type: 'CUSTOM',
|
|
290
293
|
name: 'pikku:interrupted',
|
|
291
294
|
value: {
|
|
@@ -298,7 +301,7 @@ export function wrapChannelWithAGUI(inner, options) {
|
|
|
298
301
|
}
|
|
299
302
|
// knowledge: decisions/internals/agent-speech-travels-as-a-custom-agui-event.md
|
|
300
303
|
case 'audio-delta': {
|
|
301
|
-
send({
|
|
304
|
+
await send({
|
|
302
305
|
type: 'CUSTOM',
|
|
303
306
|
name: 'pikku:audio-delta',
|
|
304
307
|
value: {
|
|
@@ -310,11 +313,11 @@ export function wrapChannelWithAGUI(inner, options) {
|
|
|
310
313
|
break;
|
|
311
314
|
}
|
|
312
315
|
case 'audio-done': {
|
|
313
|
-
send({ type: 'CUSTOM', name: 'pikku:audio-done', value: {} });
|
|
316
|
+
await send({ type: 'CUSTOM', name: 'pikku:audio-done', value: {} });
|
|
314
317
|
break;
|
|
315
318
|
}
|
|
316
319
|
case 'transcript': {
|
|
317
|
-
send({
|
|
320
|
+
await send({
|
|
318
321
|
type: 'CUSTOM',
|
|
319
322
|
name: 'pikku:transcript',
|
|
320
323
|
value: { text: event.text },
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type AbortScope } from '../../function/abort-scope.js';
|
|
1
2
|
import type { AgentStorageService } from '../../services/agent-storage-service.js';
|
|
2
3
|
/**
|
|
3
4
|
* Why an in-flight agent run was cut short.
|
|
@@ -126,7 +127,9 @@ export declare const getInFlightTools: (runId: string) => string[];
|
|
|
126
127
|
export declare const trackToolExecution: <T extends {
|
|
127
128
|
name: string;
|
|
128
129
|
readonly?: boolean;
|
|
129
|
-
execute: (input: any
|
|
130
|
+
execute: (input: any, options?: {
|
|
131
|
+
abortScope?: AbortScope;
|
|
132
|
+
}) => Promise<any>;
|
|
130
133
|
}>(tools: T[], handle: InterruptibleRunHandle) => T[];
|
|
131
134
|
export declare const trackInterruptNote: (threadId: string, write: Promise<void>) => void;
|
|
132
135
|
/** Await an interrupted tool result still being written to this thread. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from './agent-utils.js';
|
|
2
|
-
import { AbandonedError
|
|
2
|
+
import { AbandonedError } from '../../function/abort-scope.js';
|
|
3
3
|
/**
|
|
4
4
|
* Thrown out of the step loop when a run is interrupted, so an interruption is
|
|
5
5
|
* distinguishable from a genuine failure at the one `catch` that would
|
|
@@ -162,7 +162,7 @@ export const trackToolExecution = (tools, handle) => tools.map((tool) => {
|
|
|
162
162
|
}
|
|
163
163
|
return {
|
|
164
164
|
...tool,
|
|
165
|
-
execute: (input) => {
|
|
165
|
+
execute: (input, options) => {
|
|
166
166
|
// Assumed to have changed something unless it says otherwise — the tool
|
|
167
167
|
// that never declares its checkpoint is the one that cannot be assumed
|
|
168
168
|
// harmless. `beginChanges()` can only ever make this more precise.
|
|
@@ -178,7 +178,7 @@ export const trackToolExecution = (tools, handle) => tools.map((tool) => {
|
|
|
178
178
|
call.mutating = true;
|
|
179
179
|
},
|
|
180
180
|
};
|
|
181
|
-
return handle.trackTool(tool.name, randomUUID(), () =>
|
|
181
|
+
return handle.trackTool(tool.name, randomUUID(), () => tool.execute(input, { ...options, abortScope: scope }), {
|
|
182
182
|
get collectResult() {
|
|
183
183
|
return call.mutating;
|
|
184
184
|
},
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { randomUUID } from 'crypto';
|
|
2
1
|
export function resolveMemoryServices(agent, singletonServices) {
|
|
3
2
|
const memoryConfig = agent.memory;
|
|
4
3
|
const storage = memoryConfig?.storage
|
|
@@ -83,7 +82,7 @@ export async function loadContextMessages(memoryConfig, storage, input, workingM
|
|
|
83
82
|
const workingMem = await workingMemoryStorage.getWorkingMemory(input.threadId, 'thread');
|
|
84
83
|
const prompt = buildWorkingMemoryPrompt(workingMem, workingMemoryJsonSchema);
|
|
85
84
|
contextMessages.push({
|
|
86
|
-
id: `wm-${randomUUID()}`,
|
|
85
|
+
id: `wm-${globalThis.crypto.randomUUID()}`,
|
|
87
86
|
role: 'system',
|
|
88
87
|
content: prompt,
|
|
89
88
|
createdAt: new Date(),
|
|
@@ -99,9 +98,9 @@ export async function saveMessages(storage, threadId, resourceId, memoryConfig,
|
|
|
99
98
|
const newMessages = userMessage ? [userMessage] : [];
|
|
100
99
|
for (const step of result.steps) {
|
|
101
100
|
if (step.toolCalls?.length) {
|
|
102
|
-
const toolCallIds = step.toolCalls.map(() => randomUUID());
|
|
101
|
+
const toolCallIds = step.toolCalls.map(() => globalThis.crypto.randomUUID());
|
|
103
102
|
newMessages.push({
|
|
104
|
-
id: randomUUID(),
|
|
103
|
+
id: globalThis.crypto.randomUUID(),
|
|
105
104
|
role: 'assistant',
|
|
106
105
|
toolCalls: step.toolCalls.map((tc, i) => ({
|
|
107
106
|
id: toolCallIds[i],
|
|
@@ -111,7 +110,7 @@ export async function saveMessages(storage, threadId, resourceId, memoryConfig,
|
|
|
111
110
|
createdAt: new Date(),
|
|
112
111
|
});
|
|
113
112
|
newMessages.push({
|
|
114
|
-
id: randomUUID(),
|
|
113
|
+
id: globalThis.crypto.randomUUID(),
|
|
115
114
|
role: 'tool',
|
|
116
115
|
toolResults: step.toolCalls.map((tc, i) => ({
|
|
117
116
|
id: toolCallIds[i],
|
|
@@ -132,7 +131,7 @@ export async function saveMessages(storage, threadId, resourceId, memoryConfig,
|
|
|
132
131
|
]
|
|
133
132
|
: responseText;
|
|
134
133
|
newMessages.push({
|
|
135
|
-
id: randomUUID(),
|
|
134
|
+
id: globalThis.crypto.randomUUID(),
|
|
136
135
|
role: 'assistant',
|
|
137
136
|
content: assistantContent || undefined,
|
|
138
137
|
createdAt: new Date(),
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// knowledge: decisions/internals/agent-model-config-stays-a-single-resolution-seam.md
|
|
2
2
|
import { pikkuState } from '../../pikku-state.js';
|
|
3
|
+
import { readEnvVariable } from '../../env.js';
|
|
3
4
|
const isProviderQualified = (model) => model.includes('/');
|
|
4
5
|
/** `PIKKU_MODEL_ALIASES=cheap:openai/gpt-5-mini,tool:anthropic/claude-sonnet-5` */
|
|
5
6
|
const envAliases = () => {
|
|
6
|
-
const raw =
|
|
7
|
+
const raw = readEnvVariable('PIKKU_MODEL_ALIASES');
|
|
7
8
|
if (!raw)
|
|
8
9
|
return {};
|
|
9
10
|
const aliases = {};
|