@slates/client 1.0.0-rc.2
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/dist/index.cjs +409 -0
- package/dist/index.d.cts +764 -0
- package/dist/index.d.ts +764 -0
- package/dist/index.module.js +383 -0
- package/package.json +43 -0
- package/src/client.test.ts +569 -0
- package/src/client.ts +367 -0
- package/src/error.ts +157 -0
- package/src/index.ts +10 -0
- package/src/transport.ts +57 -0
- package/src/types.ts +36 -0
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
import {
|
|
2
|
+
axios,
|
|
3
|
+
Slate,
|
|
4
|
+
SlateAuth,
|
|
5
|
+
SlateConfig,
|
|
6
|
+
type SlateLogEntry,
|
|
7
|
+
SlateSpecification,
|
|
8
|
+
SlateTool,
|
|
9
|
+
SlateTrigger
|
|
10
|
+
} from '@slates/provider';
|
|
11
|
+
import { rm } from 'fs/promises';
|
|
12
|
+
import { createServer } from 'http';
|
|
13
|
+
import type { AddressInfo } from 'net';
|
|
14
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
15
|
+
import { z } from 'zod';
|
|
16
|
+
import { createLocalSlateTransport, createSlatesClient, SlateProtocolError } from './index';
|
|
17
|
+
|
|
18
|
+
let tempDirs: string[] = [];
|
|
19
|
+
|
|
20
|
+
let waitForLogs = async () => {
|
|
21
|
+
await new Promise(resolve => setTimeout(resolve, 25));
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
afterEach(async () => {
|
|
25
|
+
await Promise.all(tempDirs.splice(0).map(dir => rm(dir, { recursive: true, force: true })));
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
let createDemoSlate = () => {
|
|
29
|
+
let demoConfig = SlateConfig.create(
|
|
30
|
+
z.object({
|
|
31
|
+
prefix: z.string()
|
|
32
|
+
})
|
|
33
|
+
).getDefaultConfig(() => ({
|
|
34
|
+
prefix: 'Hello'
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
let demoAuth = SlateAuth.create<{ token: string }>()
|
|
38
|
+
.output(
|
|
39
|
+
z.object({
|
|
40
|
+
token: z.string()
|
|
41
|
+
})
|
|
42
|
+
)
|
|
43
|
+
.addTokenAuth({
|
|
44
|
+
type: 'auth.token',
|
|
45
|
+
key: 'token_auth',
|
|
46
|
+
name: 'Token Auth',
|
|
47
|
+
inputSchema: z.object({
|
|
48
|
+
token: z.string()
|
|
49
|
+
}),
|
|
50
|
+
getDefaultInput: async () => ({
|
|
51
|
+
token: 'default-token'
|
|
52
|
+
}),
|
|
53
|
+
onInputChanged: async (ctx: { newInput: { token: string } }) => ({
|
|
54
|
+
input: {
|
|
55
|
+
token: ctx.newInput.token.trim()
|
|
56
|
+
}
|
|
57
|
+
}),
|
|
58
|
+
getOutput: async (ctx: { input: { token: string } }) => ({
|
|
59
|
+
output: {
|
|
60
|
+
token: ctx.input.token
|
|
61
|
+
}
|
|
62
|
+
}),
|
|
63
|
+
getProfile: async (ctx: { output: { token: string } }) => ({
|
|
64
|
+
profile: {
|
|
65
|
+
tokenPreview: ctx.output.token.slice(0, 3)
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
let spec = SlateSpecification.create({
|
|
71
|
+
key: 'demo-slate',
|
|
72
|
+
name: 'Demo Slate',
|
|
73
|
+
description: 'A tiny test slate',
|
|
74
|
+
config: demoConfig,
|
|
75
|
+
auth: demoAuth
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
let echoTool = SlateTool.create(spec, {
|
|
79
|
+
key: 'echo',
|
|
80
|
+
name: 'Echo'
|
|
81
|
+
})
|
|
82
|
+
.input(
|
|
83
|
+
z.object({
|
|
84
|
+
name: z.string()
|
|
85
|
+
})
|
|
86
|
+
)
|
|
87
|
+
.output(
|
|
88
|
+
z.object({
|
|
89
|
+
greeting: z.string(),
|
|
90
|
+
token: z.string()
|
|
91
|
+
})
|
|
92
|
+
)
|
|
93
|
+
.scopes({
|
|
94
|
+
AND: [
|
|
95
|
+
{
|
|
96
|
+
OR: ['scope:echo', 'scope:echo:admin']
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
})
|
|
100
|
+
.handleInvocation(async ctx => ({
|
|
101
|
+
output: {
|
|
102
|
+
greeting: `${ctx.config.prefix} ${ctx.input.name}`,
|
|
103
|
+
token: ctx.auth.token
|
|
104
|
+
},
|
|
105
|
+
message: 'done'
|
|
106
|
+
}))
|
|
107
|
+
.build();
|
|
108
|
+
|
|
109
|
+
return Slate.create({
|
|
110
|
+
spec,
|
|
111
|
+
tools: [echoTool],
|
|
112
|
+
triggers: []
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
let createTraceSlate = (baseUrl: string) => {
|
|
117
|
+
let config = SlateConfig.create(z.object({}));
|
|
118
|
+
let auth = SlateAuth.create<{}>().output(z.object({}));
|
|
119
|
+
|
|
120
|
+
let spec = SlateSpecification.create({
|
|
121
|
+
key: 'trace-slate',
|
|
122
|
+
name: 'Trace Slate',
|
|
123
|
+
description: 'A slate that traces HTTP requests',
|
|
124
|
+
config,
|
|
125
|
+
auth
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
let traceTool = SlateTool.create(spec, {
|
|
129
|
+
key: 'trace_http',
|
|
130
|
+
name: 'Trace HTTP'
|
|
131
|
+
})
|
|
132
|
+
.input(z.object({}))
|
|
133
|
+
.output(
|
|
134
|
+
z.object({
|
|
135
|
+
ok: z.boolean(),
|
|
136
|
+
status: z.number(),
|
|
137
|
+
traceCount: z.number()
|
|
138
|
+
})
|
|
139
|
+
)
|
|
140
|
+
.handleInvocation(async ctx => {
|
|
141
|
+
let response = await axios.post(
|
|
142
|
+
`${baseUrl}/trace?api_key=request-secret&visible=yes`,
|
|
143
|
+
{
|
|
144
|
+
message: 'hello',
|
|
145
|
+
secret: 'client-secret'
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
headers: {
|
|
149
|
+
Authorization: 'Bearer client-token',
|
|
150
|
+
'X-Api-Key': 'client-api-key'
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
output: {
|
|
157
|
+
ok: response.data.ok,
|
|
158
|
+
status: response.status,
|
|
159
|
+
traceCount: ctx.getHttpTraces().length
|
|
160
|
+
},
|
|
161
|
+
message: 'traced'
|
|
162
|
+
};
|
|
163
|
+
})
|
|
164
|
+
.build();
|
|
165
|
+
|
|
166
|
+
return Slate.create({
|
|
167
|
+
spec,
|
|
168
|
+
tools: [traceTool],
|
|
169
|
+
triggers: []
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
let createTriggerTraceSlate = () => {
|
|
174
|
+
let config = SlateConfig.create(z.object({}));
|
|
175
|
+
let auth = SlateAuth.create<{}>().output(z.object({}));
|
|
176
|
+
|
|
177
|
+
let spec = SlateSpecification.create({
|
|
178
|
+
key: 'trigger-trace-slate',
|
|
179
|
+
name: 'Trigger Trace Slate',
|
|
180
|
+
description: 'A slate that traces trigger execution',
|
|
181
|
+
config,
|
|
182
|
+
auth
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
let pollingTrigger = SlateTrigger.create(spec, {
|
|
186
|
+
key: 'poll_trigger',
|
|
187
|
+
name: 'Poll Trigger'
|
|
188
|
+
})
|
|
189
|
+
.input(
|
|
190
|
+
z.object({
|
|
191
|
+
id: z.string()
|
|
192
|
+
})
|
|
193
|
+
)
|
|
194
|
+
.output(
|
|
195
|
+
z.object({
|
|
196
|
+
type: z.string(),
|
|
197
|
+
value: z.string()
|
|
198
|
+
})
|
|
199
|
+
)
|
|
200
|
+
.polling({
|
|
201
|
+
pollEvents: async () => ({
|
|
202
|
+
inputs: [{ id: 'poll-1' }, { id: 'poll-2' }]
|
|
203
|
+
}),
|
|
204
|
+
handleEvent: async ctx => ({
|
|
205
|
+
id: ctx.input.id,
|
|
206
|
+
type: 'poll.event',
|
|
207
|
+
output: {
|
|
208
|
+
type: 'poll.event',
|
|
209
|
+
value: ctx.input.id
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
})
|
|
213
|
+
.build();
|
|
214
|
+
|
|
215
|
+
let webhookTrigger = SlateTrigger.create(spec, {
|
|
216
|
+
key: 'webhook_trigger',
|
|
217
|
+
name: 'Webhook Trigger'
|
|
218
|
+
})
|
|
219
|
+
.input(
|
|
220
|
+
z.object({
|
|
221
|
+
id: z.string()
|
|
222
|
+
})
|
|
223
|
+
)
|
|
224
|
+
.output(
|
|
225
|
+
z.object({
|
|
226
|
+
type: z.string(),
|
|
227
|
+
value: z.string()
|
|
228
|
+
})
|
|
229
|
+
)
|
|
230
|
+
.webhook({
|
|
231
|
+
handleRequest: async () => ({
|
|
232
|
+
inputs: [{ id: 'webhook-1' }, { id: 'webhook-2' }]
|
|
233
|
+
}),
|
|
234
|
+
handleEvent: async ctx => ({
|
|
235
|
+
id: ctx.input.id,
|
|
236
|
+
type: 'webhook.event',
|
|
237
|
+
output: {
|
|
238
|
+
type: 'webhook.event',
|
|
239
|
+
value: ctx.input.id
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
})
|
|
243
|
+
.build();
|
|
244
|
+
|
|
245
|
+
return Slate.create({
|
|
246
|
+
spec,
|
|
247
|
+
tools: [],
|
|
248
|
+
triggers: [pollingTrigger, webhookTrigger]
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
describe('@slates/client local transport', () => {
|
|
253
|
+
it('discovers auth/config and invokes tools with session state', async () => {
|
|
254
|
+
let slate = createDemoSlate();
|
|
255
|
+
let client = createSlatesClient({
|
|
256
|
+
transport: createLocalSlateTransport({ slate })
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
let provider = await client.identify();
|
|
260
|
+
expect(provider.provider.id).toBe('demo-slate');
|
|
261
|
+
|
|
262
|
+
let actions = await client.listTools();
|
|
263
|
+
expect(actions).toHaveLength(1);
|
|
264
|
+
expect(actions[0]!.id).toBe('echo');
|
|
265
|
+
expect(actions[0]!.scopes).toEqual({
|
|
266
|
+
AND: [
|
|
267
|
+
{
|
|
268
|
+
OR: ['scope:echo', 'scope:echo:admin']
|
|
269
|
+
}
|
|
270
|
+
]
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
let configSchema = await client.getConfigSchema();
|
|
274
|
+
expect(configSchema.schema.properties.prefix.type).toBe('string');
|
|
275
|
+
|
|
276
|
+
let defaultConfig = await client.getDefaultConfig();
|
|
277
|
+
expect(defaultConfig.config).toEqual({ prefix: 'Hello' });
|
|
278
|
+
|
|
279
|
+
let authMethods = await client.listAuthMethods();
|
|
280
|
+
expect(authMethods.authenticationMethods).toHaveLength(1);
|
|
281
|
+
expect(authMethods.authenticationMethods[0]!.type).toBe('auth.token');
|
|
282
|
+
|
|
283
|
+
let defaultInput = await client.getDefaultAuthInput('token_auth');
|
|
284
|
+
expect(defaultInput.input).toEqual({ token: 'default-token' });
|
|
285
|
+
|
|
286
|
+
let changedInput = await client.updateAuthInput({
|
|
287
|
+
authenticationMethodId: 'token_auth',
|
|
288
|
+
previousInput: null,
|
|
289
|
+
newInput: { token: ' trimmed-token ' }
|
|
290
|
+
});
|
|
291
|
+
expect(changedInput.input).toEqual({ token: 'trimmed-token' });
|
|
292
|
+
|
|
293
|
+
let authOutput = await client.getAuthOutput({
|
|
294
|
+
authenticationMethodId: 'token_auth',
|
|
295
|
+
input: changedInput.input ?? { token: '' }
|
|
296
|
+
});
|
|
297
|
+
expect(authOutput.output).toEqual({ token: 'trimmed-token' });
|
|
298
|
+
|
|
299
|
+
client.setConfig({ prefix: 'Hi' });
|
|
300
|
+
client.setAuth({
|
|
301
|
+
authenticationMethodId: 'token_auth',
|
|
302
|
+
output: authOutput.output
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
let result = await client.invokeTool('echo', { name: 'Tobias' });
|
|
306
|
+
expect(result.output).toEqual({
|
|
307
|
+
greeting: 'Hi Tobias',
|
|
308
|
+
token: 'trimmed-token'
|
|
309
|
+
});
|
|
310
|
+
expect(client.state.session?.id).toBeTruthy();
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('emits structured traces for provider callbacks', async () => {
|
|
314
|
+
let slate = createDemoSlate();
|
|
315
|
+
let logs: SlateLogEntry[] = [];
|
|
316
|
+
let client = createSlatesClient({
|
|
317
|
+
transport: createLocalSlateTransport({
|
|
318
|
+
slate,
|
|
319
|
+
listeners: [
|
|
320
|
+
entries => {
|
|
321
|
+
logs.push(...entries);
|
|
322
|
+
}
|
|
323
|
+
]
|
|
324
|
+
})
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
await client.identify();
|
|
328
|
+
await client.getDefaultConfig();
|
|
329
|
+
let changedInput = await client.updateAuthInput({
|
|
330
|
+
authenticationMethodId: 'token_auth',
|
|
331
|
+
previousInput: null,
|
|
332
|
+
newInput: { token: ' traced-token ' }
|
|
333
|
+
});
|
|
334
|
+
let authOutput = await client.getAuthOutput({
|
|
335
|
+
authenticationMethodId: 'token_auth',
|
|
336
|
+
input: changedInput.input ?? { token: '' }
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
client.setConfig({ prefix: 'Hi' });
|
|
340
|
+
client.setAuth({
|
|
341
|
+
authenticationMethodId: 'token_auth',
|
|
342
|
+
output: authOutput.output
|
|
343
|
+
});
|
|
344
|
+
await client.invokeTool('echo', { name: 'Tracing' });
|
|
345
|
+
|
|
346
|
+
await waitForLogs();
|
|
347
|
+
|
|
348
|
+
expect(logs).toEqual(
|
|
349
|
+
expect.arrayContaining([
|
|
350
|
+
expect.objectContaining({
|
|
351
|
+
type: 'info',
|
|
352
|
+
message: 'Getting default config',
|
|
353
|
+
data: expect.objectContaining({
|
|
354
|
+
providerId: 'demo-slate',
|
|
355
|
+
component: 'config',
|
|
356
|
+
functionName: 'getDefaultConfig',
|
|
357
|
+
phase: 'start'
|
|
358
|
+
})
|
|
359
|
+
}),
|
|
360
|
+
expect.objectContaining({
|
|
361
|
+
type: 'info',
|
|
362
|
+
message: 'Authentication input change handler completed',
|
|
363
|
+
data: expect.objectContaining({
|
|
364
|
+
providerId: 'demo-slate',
|
|
365
|
+
component: 'auth',
|
|
366
|
+
functionName: 'onInputChanged',
|
|
367
|
+
phase: 'success',
|
|
368
|
+
authenticationMethodId: 'token_auth',
|
|
369
|
+
returnedInput: true
|
|
370
|
+
})
|
|
371
|
+
}),
|
|
372
|
+
expect.objectContaining({
|
|
373
|
+
type: 'info',
|
|
374
|
+
message: 'Completed tool "Echo" (echo)',
|
|
375
|
+
data: expect.objectContaining({
|
|
376
|
+
providerId: 'demo-slate',
|
|
377
|
+
component: 'action',
|
|
378
|
+
functionName: 'handleInvocation',
|
|
379
|
+
phase: 'success',
|
|
380
|
+
actionId: 'echo',
|
|
381
|
+
hasMessage: true,
|
|
382
|
+
actionResultMessage: 'done'
|
|
383
|
+
})
|
|
384
|
+
})
|
|
385
|
+
])
|
|
386
|
+
);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('emits human-readable trigger event count logs', async () => {
|
|
390
|
+
let logs: SlateLogEntry[] = [];
|
|
391
|
+
let client = createSlatesClient({
|
|
392
|
+
transport: createLocalSlateTransport({
|
|
393
|
+
slate: createTriggerTraceSlate(),
|
|
394
|
+
listeners: [
|
|
395
|
+
entries => {
|
|
396
|
+
logs.push(...entries);
|
|
397
|
+
}
|
|
398
|
+
]
|
|
399
|
+
}),
|
|
400
|
+
state: {
|
|
401
|
+
config: {}
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
client.ensureSession();
|
|
406
|
+
|
|
407
|
+
await client.request('slates/action.trigger.poll_events', {
|
|
408
|
+
actionId: 'poll_trigger',
|
|
409
|
+
state: null
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
await client.request('slates/action.trigger.webhook_handle', {
|
|
413
|
+
actionId: 'webhook_trigger',
|
|
414
|
+
url: 'https://example.com/webhook',
|
|
415
|
+
method: 'POST',
|
|
416
|
+
headers: {
|
|
417
|
+
'content-type': 'application/json'
|
|
418
|
+
},
|
|
419
|
+
body: null,
|
|
420
|
+
state: null
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
await waitForLogs();
|
|
424
|
+
|
|
425
|
+
expect(logs).toEqual(
|
|
426
|
+
expect.arrayContaining([
|
|
427
|
+
expect.objectContaining({
|
|
428
|
+
type: 'info',
|
|
429
|
+
message: 'Polled 2 event(s) for trigger "Poll Trigger" (poll_trigger)',
|
|
430
|
+
data: expect.objectContaining({
|
|
431
|
+
component: 'action',
|
|
432
|
+
functionName: 'pollEvents',
|
|
433
|
+
inputCount: 2,
|
|
434
|
+
actionId: 'poll_trigger'
|
|
435
|
+
})
|
|
436
|
+
}),
|
|
437
|
+
expect.objectContaining({
|
|
438
|
+
type: 'info',
|
|
439
|
+
message:
|
|
440
|
+
'Received 2 webhook event(s) for trigger "Webhook Trigger" (webhook_trigger)',
|
|
441
|
+
data: expect.objectContaining({
|
|
442
|
+
component: 'action',
|
|
443
|
+
functionName: 'handleRequest',
|
|
444
|
+
inputCount: 2,
|
|
445
|
+
actionId: 'webhook_trigger'
|
|
446
|
+
})
|
|
447
|
+
})
|
|
448
|
+
])
|
|
449
|
+
);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it('throws structured protocol errors for provider responses', async () => {
|
|
453
|
+
let client = createSlatesClient({
|
|
454
|
+
transport: {
|
|
455
|
+
async send(messages) {
|
|
456
|
+
let request = messages.find(message => 'id' in message && message.id);
|
|
457
|
+
return [
|
|
458
|
+
{
|
|
459
|
+
jsonrpc: '2.0',
|
|
460
|
+
id: (request as { id: string }).id,
|
|
461
|
+
error: {
|
|
462
|
+
code: 'resource.not_found',
|
|
463
|
+
kind: 'upstream',
|
|
464
|
+
message: 'Resource contact_123 was not found',
|
|
465
|
+
status: 404,
|
|
466
|
+
provider: {
|
|
467
|
+
service: 'demo',
|
|
468
|
+
operation: 'failing.invoke'
|
|
469
|
+
},
|
|
470
|
+
baggage: {
|
|
471
|
+
resourceId: 'contact_123'
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
} as any
|
|
475
|
+
];
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
let promise = client.identify();
|
|
481
|
+
|
|
482
|
+
await expect(promise).rejects.toBeInstanceOf(SlateProtocolError);
|
|
483
|
+
|
|
484
|
+
try {
|
|
485
|
+
await promise;
|
|
486
|
+
} catch (error) {
|
|
487
|
+
expect(error).toBeInstanceOf(SlateProtocolError);
|
|
488
|
+
expect((error as SlateProtocolError).data).toMatchObject({
|
|
489
|
+
code: 'resource.not_found',
|
|
490
|
+
kind: 'upstream',
|
|
491
|
+
message: 'Resource contact_123 was not found',
|
|
492
|
+
status: 404,
|
|
493
|
+
provider: {
|
|
494
|
+
service: 'demo',
|
|
495
|
+
operation: 'failing.invoke'
|
|
496
|
+
},
|
|
497
|
+
baggage: {
|
|
498
|
+
resourceId: 'contact_123'
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it('returns sanitized request traces for shared axios calls', async () => {
|
|
505
|
+
let server = createServer((_req, res) => {
|
|
506
|
+
res.statusCode = 200;
|
|
507
|
+
res.setHeader('Content-Type', 'application/json');
|
|
508
|
+
res.setHeader('X-Request-Id', 'req-123');
|
|
509
|
+
res.end(
|
|
510
|
+
JSON.stringify({
|
|
511
|
+
ok: true,
|
|
512
|
+
token: 'server-secret',
|
|
513
|
+
note: 'x'.repeat(11_000)
|
|
514
|
+
})
|
|
515
|
+
);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
await new Promise<void>((resolve, reject) => {
|
|
519
|
+
server.once('error', reject);
|
|
520
|
+
server.listen(0, '127.0.0.1', () => resolve());
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
try {
|
|
524
|
+
let { port } = server.address() as AddressInfo;
|
|
525
|
+
let client = createSlatesClient({
|
|
526
|
+
transport: createLocalSlateTransport({
|
|
527
|
+
slate: createTraceSlate(`http://127.0.0.1:${port}`)
|
|
528
|
+
}),
|
|
529
|
+
state: {
|
|
530
|
+
config: {}
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
let result = await client.invokeTool('trace_http', {});
|
|
535
|
+
|
|
536
|
+
expect(result.output).toEqual({
|
|
537
|
+
ok: true,
|
|
538
|
+
status: 200,
|
|
539
|
+
traceCount: 1
|
|
540
|
+
});
|
|
541
|
+
expect(result.requestTraces).toHaveLength(1);
|
|
542
|
+
|
|
543
|
+
let trace = result.requestTraces?.[0];
|
|
544
|
+
expect(trace?.request.method).toBe('POST');
|
|
545
|
+
expect(trace?.request.url).toContain('visible=yes');
|
|
546
|
+
expect(trace?.request.url).not.toContain('request-secret');
|
|
547
|
+
expect(trace?.request.headers).toMatchObject({
|
|
548
|
+
accept: 'application/json, text/plain, */*',
|
|
549
|
+
'user-agent': 'slates.dev@1.0.0/trace-slate',
|
|
550
|
+
'x-slates-provider': 'trace-slate'
|
|
551
|
+
});
|
|
552
|
+
expect(trace?.request.headers).not.toHaveProperty('authorization');
|
|
553
|
+
expect(trace?.request.headers).not.toHaveProperty('x-api-key');
|
|
554
|
+
expect(trace?.request.body?.text).toContain('"secret":"[redacted]"');
|
|
555
|
+
expect(trace?.request.body?.text).not.toContain('client-secret');
|
|
556
|
+
expect(trace?.response?.headers).toMatchObject({
|
|
557
|
+
'content-type': 'application/json',
|
|
558
|
+
'x-request-id': 'req-123'
|
|
559
|
+
});
|
|
560
|
+
expect(trace?.response?.body?.truncated).toBe(true);
|
|
561
|
+
expect(trace?.response?.body?.text).toContain('"token":"[redacted]"');
|
|
562
|
+
expect(trace?.response?.body?.text).not.toContain('server-secret');
|
|
563
|
+
} finally {
|
|
564
|
+
await new Promise<void>((resolve, reject) => {
|
|
565
|
+
server.close(error => (error ? reject(error) : resolve()));
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
});
|