langwatch 1.18.0 → 1.20.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 +25 -11
- package/dist/agent/index.d.mts +122 -79
- package/dist/agent/index.d.ts +122 -79
- package/dist/agent/index.js +523 -300
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/index.mjs +522 -301
- package/dist/agent/index.mjs.map +1 -1
- package/dist/chunk-5VYEG3RS.js +117 -0
- package/dist/chunk-5VYEG3RS.js.map +1 -0
- package/dist/{chunk-O4J3ZPZY.js → chunk-5ZNEXSAB.js} +7912 -2564
- package/dist/chunk-5ZNEXSAB.js.map +1 -0
- package/dist/chunk-BDQ2RG7R.mjs +852 -0
- package/dist/chunk-BDQ2RG7R.mjs.map +1 -0
- package/dist/chunk-BL3GHEKF.js +59 -0
- package/dist/chunk-BL3GHEKF.js.map +1 -0
- package/dist/chunk-C7IWJ2GJ.mjs +59 -0
- package/dist/chunk-C7IWJ2GJ.mjs.map +1 -0
- package/dist/chunk-EQ5CFQGJ.js +852 -0
- package/dist/chunk-EQ5CFQGJ.js.map +1 -0
- package/dist/{chunk-NPEK4RSD.mjs → chunk-ET5KYCG2.mjs} +7757 -2409
- package/dist/chunk-ET5KYCG2.mjs.map +1 -0
- package/dist/{chunk-MVQJMRM6.mjs → chunk-FKYYEGJ7.mjs} +120 -112
- package/dist/chunk-FKYYEGJ7.mjs.map +1 -0
- package/dist/{chunk-JJASOJOK.js → chunk-HCK7TCSK.js} +138 -130
- package/dist/chunk-HCK7TCSK.js.map +1 -0
- package/dist/{chunk-LCG5Y2EG.js → chunk-N76BG37W.js} +15 -101
- package/dist/chunk-N76BG37W.js.map +1 -0
- package/dist/chunk-SERULD4Z.mjs +117 -0
- package/dist/chunk-SERULD4Z.mjs.map +1 -0
- package/dist/{chunk-73QOHSHU.js → chunk-WDUUAIG3.js} +2 -2
- package/dist/chunk-WDUUAIG3.js.map +1 -0
- package/dist/{chunk-WMDQVWA6.mjs → chunk-WSGVZ3J2.mjs} +22 -108
- package/dist/chunk-WSGVZ3J2.mjs.map +1 -0
- package/dist/{chunk-57L2K66L.mjs → chunk-XQ4CP2JK.mjs} +2 -2
- package/dist/chunk-XQ4CP2JK.mjs.map +1 -0
- package/dist/cli/bundle.js +766 -479
- package/dist/implementation-B4Z2ksnA.d.mts +261 -0
- package/dist/implementation-BFUgzz5C.d.ts +261 -0
- package/dist/index.d.mts +398 -1123
- package/dist/index.d.ts +398 -1123
- package/dist/index.js +7613 -3566
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +7419 -3372
- package/dist/index.mjs.map +1 -1
- package/dist/observability-sdk/index.d.mts +16 -76
- package/dist/observability-sdk/index.d.ts +16 -76
- package/dist/observability-sdk/index.js +8 -5
- package/dist/observability-sdk/index.js.map +1 -1
- package/dist/observability-sdk/index.mjs +8 -5
- package/dist/observability-sdk/instrumentation/langchain/index.d.mts +3 -5
- package/dist/observability-sdk/instrumentation/langchain/index.d.ts +3 -5
- package/dist/observability-sdk/instrumentation/langchain/index.js +160 -112
- package/dist/observability-sdk/instrumentation/langchain/index.js.map +1 -1
- package/dist/observability-sdk/instrumentation/langchain/index.mjs +148 -100
- package/dist/observability-sdk/instrumentation/langchain/index.mjs.map +1 -1
- package/dist/observability-sdk/semconv/index.d.mts +37 -0
- package/dist/observability-sdk/semconv/index.d.ts +37 -0
- package/dist/observability-sdk/semconv/index.js +121 -0
- package/dist/observability-sdk/semconv/index.js.map +1 -0
- package/dist/observability-sdk/semconv/index.mjs +121 -0
- package/dist/observability-sdk/semconv/index.mjs.map +1 -0
- package/dist/observability-sdk/setup/node/index.d.mts +73 -313
- package/dist/observability-sdk/setup/node/index.d.ts +73 -313
- package/dist/observability-sdk/setup/node/index.js +6 -4
- package/dist/observability-sdk/setup/node/index.js.map +1 -1
- package/dist/observability-sdk/setup/node/index.mjs +5 -3
- package/dist/types-B696jWYQ.d.mts +16010 -0
- package/dist/types-Bo6OvFct.d.ts +16010 -0
- package/dist/{types-VOZv9LYO.d.mts → types-CoS5PmeU.d.mts} +22 -59
- package/dist/{types-VOZv9LYO.d.ts → types-CoS5PmeU.d.ts} +22 -59
- package/dist/{types-DLfhJb38.d.mts → types-RkiBQZ9j.d.mts} +1 -6
- package/dist/{types-DLfhJb38.d.ts → types-RkiBQZ9j.d.ts} +1 -6
- package/dist/values-1yj9l0Sa.d.mts +112 -0
- package/dist/values-1yj9l0Sa.d.ts +112 -0
- package/package.json +95 -64
- package/dist/chunk-57L2K66L.mjs.map +0 -1
- package/dist/chunk-73QOHSHU.js.map +0 -1
- package/dist/chunk-HY2N6TO5.mjs +0 -566
- package/dist/chunk-HY2N6TO5.mjs.map +0 -1
- package/dist/chunk-JJASOJOK.js.map +0 -1
- package/dist/chunk-LCG5Y2EG.js.map +0 -1
- package/dist/chunk-MVQJMRM6.mjs.map +0 -1
- package/dist/chunk-NPEK4RSD.mjs.map +0 -1
- package/dist/chunk-O4J3ZPZY.js.map +0 -1
- package/dist/chunk-UEJSGC3P.js +0 -566
- package/dist/chunk-UEJSGC3P.js.map +0 -1
- package/dist/chunk-WMDQVWA6.mjs.map +0 -1
- package/dist/implementation-BvX-7vox.d.ts +0 -716
- package/dist/implementation-CQ__V0Vc.d.mts +0 -716
- package/dist/types-CNEUr6Hm.d.ts +0 -36682
- package/dist/types-DfPAHmAh.d.mts +0 -36682
package/dist/agent/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// ../../node_modules/.pnpm/tsup@8.5.1_jiti@2.7.0_postcss@8.5.
|
|
1
|
+
// ../../node_modules/.pnpm/tsup@8.5.1_jiti@2.7.0_postcss@8.5.26_supports-color@10.2.2_tsx@4.22.4_typescript@6.0.3_yaml@2.9.0/node_modules/tsup/assets/esm_shims.js
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
4
|
var getFilename = () => fileURLToPath(import.meta.url);
|
|
@@ -40,11 +40,11 @@ var ConsoleLogger = class {
|
|
|
40
40
|
import { context, propagation, trace } from "@opentelemetry/api";
|
|
41
41
|
|
|
42
42
|
// src/agent/identity.ts
|
|
43
|
-
import * as os from "os";
|
|
44
43
|
import { randomUUID } from "crypto";
|
|
44
|
+
import * as os from "os";
|
|
45
45
|
|
|
46
46
|
// package.json
|
|
47
|
-
var version = "1.
|
|
47
|
+
var version = "1.20.0";
|
|
48
48
|
|
|
49
49
|
// src/internal/constants.ts
|
|
50
50
|
var LANGWATCH_SDK_VERSION = version;
|
|
@@ -102,7 +102,9 @@ function resolveEnabled({
|
|
|
102
102
|
env = process.env
|
|
103
103
|
}) {
|
|
104
104
|
const flag = env.LANGWATCH_AGENT_CONNECT;
|
|
105
|
-
if (isSet2(flag)
|
|
105
|
+
if (isSet2(flag)) {
|
|
106
|
+
if (!isTruthy(flag)) return false;
|
|
107
|
+
}
|
|
106
108
|
if (explicit !== void 0) return explicit;
|
|
107
109
|
return !isTruthy(env.CI);
|
|
108
110
|
}
|
|
@@ -185,24 +187,37 @@ var isStringList = (value) => Array.isArray(value) && value.every(isString);
|
|
|
185
187
|
var readScope = (value) => {
|
|
186
188
|
if (!isRecord(value)) return { kind: "shared" };
|
|
187
189
|
if (value.kind === "owner") return { kind: "owner" };
|
|
188
|
-
if (value.kind === "host" && isString(value.hostLabel))
|
|
190
|
+
if (value.kind === "host" && isString(value.hostLabel))
|
|
191
|
+
return { kind: "host", hostLabel: value.hostLabel };
|
|
189
192
|
return { kind: "shared" };
|
|
190
193
|
};
|
|
194
|
+
var readRegisteredAgent = (entry) => {
|
|
195
|
+
if (!isRecord(entry)) return null;
|
|
196
|
+
if (!isString(entry.name)) return null;
|
|
197
|
+
let id = null;
|
|
198
|
+
if (isString(entry.id)) {
|
|
199
|
+
id = entry.id;
|
|
200
|
+
} else if (isString(entry.agentId)) {
|
|
201
|
+
id = entry.agentId;
|
|
202
|
+
}
|
|
203
|
+
if (id === null) return null;
|
|
204
|
+
return {
|
|
205
|
+
name: entry.name,
|
|
206
|
+
environment: isString(entry.environment) ? entry.environment : "",
|
|
207
|
+
id,
|
|
208
|
+
url: isString(entry.url) ? entry.url : "",
|
|
209
|
+
parameterNotes: isStringList(entry.parameterNotes) ? entry.parameterNotes : [],
|
|
210
|
+
scope: readScope(entry.scope)
|
|
211
|
+
};
|
|
212
|
+
};
|
|
191
213
|
var readRegistered = (frame) => {
|
|
192
|
-
if (!Array.isArray(frame.agents)
|
|
214
|
+
if (!Array.isArray(frame.agents)) return null;
|
|
215
|
+
if (!isString(frame.instanceId)) return null;
|
|
193
216
|
const agents = [];
|
|
194
217
|
for (const entry of frame.agents) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
agents.push({
|
|
199
|
-
name: entry.name,
|
|
200
|
-
environment: isString(entry.environment) ? entry.environment : "",
|
|
201
|
-
id,
|
|
202
|
-
url: isString(entry.url) ? entry.url : "",
|
|
203
|
-
parameterNotes: isStringList(entry.parameterNotes) ? entry.parameterNotes : [],
|
|
204
|
-
scope: readScope(entry.scope)
|
|
205
|
-
});
|
|
218
|
+
const agent = readRegisteredAgent(entry);
|
|
219
|
+
if (agent === null) return null;
|
|
220
|
+
agents.push(agent);
|
|
206
221
|
}
|
|
207
222
|
return {
|
|
208
223
|
type: "registered",
|
|
@@ -238,12 +253,15 @@ var readDeadline = (value) => {
|
|
|
238
253
|
return null;
|
|
239
254
|
};
|
|
240
255
|
var readCall = (frame) => {
|
|
241
|
-
if (!isString(frame.callId)
|
|
256
|
+
if (!isString(frame.callId)) return null;
|
|
257
|
+
if (!isString(frame.agentId)) return null;
|
|
242
258
|
const messages = isMessageList(frame.messages) ? frame.messages : [];
|
|
243
259
|
const run = isRecord(frame.run) ? frame.run : {};
|
|
244
260
|
return {
|
|
245
261
|
type: "call",
|
|
246
262
|
protocol: typeof frame.protocol === "number" ? frame.protocol : PROTOCOL_VERSION,
|
|
263
|
+
// A platform that predates hooks sends no kind: every call is a turn.
|
|
264
|
+
kind: isString(frame.kind) ? frame.kind : "turn",
|
|
247
265
|
callId: frame.callId,
|
|
248
266
|
agentId: frame.agentId,
|
|
249
267
|
threadId: isString(frame.threadId) ? frame.threadId : "",
|
|
@@ -251,6 +269,7 @@ var readCall = (frame) => {
|
|
|
251
269
|
newMessages: isMessageList(frame.newMessages) ? frame.newMessages : messages,
|
|
252
270
|
params: readParams(frame.params),
|
|
253
271
|
session: frame.session === void 0 ? null : frame.session,
|
|
272
|
+
state: isRecord(frame.state) ? frame.state : null,
|
|
254
273
|
traceparent: isString(frame.traceparent) ? frame.traceparent : null,
|
|
255
274
|
deadlineAt: readDeadline(frame.deadlineAt),
|
|
256
275
|
run: {
|
|
@@ -267,7 +286,8 @@ function parseServerFrame(raw) {
|
|
|
267
286
|
} catch {
|
|
268
287
|
return null;
|
|
269
288
|
}
|
|
270
|
-
if (!isRecord(parsed)
|
|
289
|
+
if (!isRecord(parsed)) return null;
|
|
290
|
+
if (!isString(parsed.type)) return null;
|
|
271
291
|
switch (parsed.type) {
|
|
272
292
|
case "registered":
|
|
273
293
|
return readRegistered(parsed);
|
|
@@ -296,164 +316,6 @@ function traceIdFromTraceparent(traceparent) {
|
|
|
296
316
|
return traceId.toLowerCase();
|
|
297
317
|
}
|
|
298
318
|
|
|
299
|
-
// src/agent/schema.ts
|
|
300
|
-
var AgentParameterError = class extends Error {
|
|
301
|
-
constructor(message) {
|
|
302
|
-
super(message);
|
|
303
|
-
this.code = "agent_parameter_invalid";
|
|
304
|
-
this.name = "AgentParameterError";
|
|
305
|
-
}
|
|
306
|
-
};
|
|
307
|
-
var ACCEPTED_FORMS = `parameters must be a definition map ({ model: { options: [...], default: '...' } }), a Standard JSON Schema object (one with "~standard".jsonSchema), or a JSON Schema object ({ type: 'object', properties })`;
|
|
308
|
-
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
309
|
-
var isStandardJsonSchema = (value) => {
|
|
310
|
-
if (!isRecord2(value)) return false;
|
|
311
|
-
const standard = value["~standard"];
|
|
312
|
-
return isRecord2(standard) && isRecord2(standard.jsonSchema);
|
|
313
|
-
};
|
|
314
|
-
var isJsonSchemaObject = (value) => isRecord2(value) && value.type === "object" && isRecord2(value.properties);
|
|
315
|
-
var isDefinitionMap = (value) => isRecord2(value) && Object.values(value).every(isRecord2);
|
|
316
|
-
var definitionType = (definition) => {
|
|
317
|
-
if (definition.type) return definition.type;
|
|
318
|
-
if (definition.options) return "string";
|
|
319
|
-
const value = definition.default;
|
|
320
|
-
if (typeof value === "number") return "number";
|
|
321
|
-
if (typeof value === "boolean") return "boolean";
|
|
322
|
-
return "string";
|
|
323
|
-
};
|
|
324
|
-
var definitionMapToSchema = (definitions) => {
|
|
325
|
-
const properties = {};
|
|
326
|
-
const required = [];
|
|
327
|
-
for (const [name, definition] of Object.entries(definitions)) {
|
|
328
|
-
const property = { type: definitionType(definition) };
|
|
329
|
-
if (definition.options) property.enum = [...definition.options];
|
|
330
|
-
if (definition.default !== void 0) property.default = definition.default;
|
|
331
|
-
else required.push(name);
|
|
332
|
-
if (definition.description !== void 0) property.description = definition.description;
|
|
333
|
-
properties[name] = property;
|
|
334
|
-
}
|
|
335
|
-
const schema = { type: "object", properties };
|
|
336
|
-
if (required.length > 0) schema.required = required;
|
|
337
|
-
return schema;
|
|
338
|
-
};
|
|
339
|
-
var readStandardJsonSchema = (input) => {
|
|
340
|
-
const converter = input["~standard"].jsonSchema;
|
|
341
|
-
const options = { target: "draft-2020-12" };
|
|
342
|
-
const schema = converter.input?.(options) ?? converter.output?.(options);
|
|
343
|
-
if (schema === void 0) {
|
|
344
|
-
throw new AgentParameterError(`the "~standard".jsonSchema converter has no input function; ${ACCEPTED_FORMS}`);
|
|
345
|
-
}
|
|
346
|
-
if (!isRecord2(schema)) {
|
|
347
|
-
throw new AgentParameterError(`the "~standard".jsonSchema converter returned no object; ${ACCEPTED_FORMS}`);
|
|
348
|
-
}
|
|
349
|
-
return schema;
|
|
350
|
-
};
|
|
351
|
-
function toParameterSchema(input) {
|
|
352
|
-
if (input === void 0) return { type: "object", properties: {} };
|
|
353
|
-
if (isStandardJsonSchema(input)) return readStandardJsonSchema(input);
|
|
354
|
-
if (isJsonSchemaObject(input)) return input;
|
|
355
|
-
if (isDefinitionMap(input)) return definitionMapToSchema(input);
|
|
356
|
-
throw new AgentParameterError(ACCEPTED_FORMS);
|
|
357
|
-
}
|
|
358
|
-
var specType = (property) => {
|
|
359
|
-
const type = Array.isArray(property.type) ? property.type.find((item) => item !== "null") : property.type;
|
|
360
|
-
if (type === "number" || type === "integer") return "number";
|
|
361
|
-
if (type === "boolean") return "boolean";
|
|
362
|
-
return "string";
|
|
363
|
-
};
|
|
364
|
-
var scalar = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : void 0;
|
|
365
|
-
function parameterSpecsFromSchema(schema) {
|
|
366
|
-
const properties = isRecord2(schema.properties) ? schema.properties : {};
|
|
367
|
-
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
368
|
-
const specs = [];
|
|
369
|
-
for (const [name, raw] of Object.entries(properties)) {
|
|
370
|
-
const property = isRecord2(raw) ? raw : {};
|
|
371
|
-
const spec = { name, type: specType(property) };
|
|
372
|
-
const options = Array.isArray(property.enum) ? property.enum.filter((item) => typeof item === "string") : void 0;
|
|
373
|
-
if (options && options.length > 0) spec.options = options;
|
|
374
|
-
const fallback = scalar(property.default);
|
|
375
|
-
if (fallback !== void 0) spec.default = fallback;
|
|
376
|
-
if (typeof property.description === "string") spec.description = property.description;
|
|
377
|
-
spec.required = fallback === void 0 && required.has(name);
|
|
378
|
-
specs.push(spec);
|
|
379
|
-
}
|
|
380
|
-
return specs;
|
|
381
|
-
}
|
|
382
|
-
var coerce = ({
|
|
383
|
-
spec,
|
|
384
|
-
value
|
|
385
|
-
}) => {
|
|
386
|
-
if (spec.type === "number") {
|
|
387
|
-
const asNumber = typeof value === "number" ? value : Number(value);
|
|
388
|
-
if (typeof value === "boolean" || !Number.isFinite(asNumber) || String(value).trim() === "") {
|
|
389
|
-
throw new AgentParameterError(`parameter "${spec.name}" must be a number, got ${JSON.stringify(value)}`);
|
|
390
|
-
}
|
|
391
|
-
return asNumber;
|
|
392
|
-
}
|
|
393
|
-
if (spec.type === "boolean") {
|
|
394
|
-
if (typeof value === "boolean") return value;
|
|
395
|
-
if (value === "true") return true;
|
|
396
|
-
if (value === "false") return false;
|
|
397
|
-
throw new AgentParameterError(`parameter "${spec.name}" must be true or false, got ${JSON.stringify(value)}`);
|
|
398
|
-
}
|
|
399
|
-
const asString = typeof value === "string" ? value : String(value);
|
|
400
|
-
if (spec.options && !spec.options.includes(asString)) {
|
|
401
|
-
throw new AgentParameterError(
|
|
402
|
-
`parameter "${spec.name}" must be one of ${spec.options.join(", ")}, got ${JSON.stringify(value)}`
|
|
403
|
-
);
|
|
404
|
-
}
|
|
405
|
-
return asString;
|
|
406
|
-
};
|
|
407
|
-
function resolveParameterValues({
|
|
408
|
-
specs,
|
|
409
|
-
supplied
|
|
410
|
-
}) {
|
|
411
|
-
const values = { ...supplied ?? {} };
|
|
412
|
-
for (const spec of specs) {
|
|
413
|
-
const value = values[spec.name];
|
|
414
|
-
if (value === void 0) {
|
|
415
|
-
if (spec.default !== void 0) {
|
|
416
|
-
values[spec.name] = spec.default;
|
|
417
|
-
continue;
|
|
418
|
-
}
|
|
419
|
-
if (!spec.required) continue;
|
|
420
|
-
throw new AgentParameterError(`parameter "${spec.name}" is required and the run did not supply it`);
|
|
421
|
-
}
|
|
422
|
-
values[spec.name] = coerce({ spec, value });
|
|
423
|
-
}
|
|
424
|
-
return values;
|
|
425
|
-
}
|
|
426
|
-
var issuePath = (issue) => (issue.path ?? []).map(
|
|
427
|
-
(segment) => typeof segment === "object" && segment !== null ? String(segment.key) : String(segment)
|
|
428
|
-
).join(".");
|
|
429
|
-
async function validateParameterValues({
|
|
430
|
-
schema,
|
|
431
|
-
values
|
|
432
|
-
}) {
|
|
433
|
-
const standard = schema["~standard"];
|
|
434
|
-
const result = await standard.validate?.(values);
|
|
435
|
-
if (result === void 0) return values;
|
|
436
|
-
if (result.issues) {
|
|
437
|
-
const detail = result.issues.map((issue) => {
|
|
438
|
-
const path2 = issuePath(issue);
|
|
439
|
-
return path2 ? `${path2}: ${issue.message}` : issue.message;
|
|
440
|
-
}).join("; ");
|
|
441
|
-
throw new AgentParameterError(`parameters refused by the schema: ${detail}`);
|
|
442
|
-
}
|
|
443
|
-
const parsed = isRecord2(result.value) ? result.value : {};
|
|
444
|
-
return { ...values, ...parsed };
|
|
445
|
-
}
|
|
446
|
-
function createParameterReader({
|
|
447
|
-
input,
|
|
448
|
-
specs
|
|
449
|
-
}) {
|
|
450
|
-
const schema = isStandardJsonSchema(input) ? input : void 0;
|
|
451
|
-
return async (supplied) => {
|
|
452
|
-
const values = resolveParameterValues({ specs, supplied });
|
|
453
|
-
return schema ? validateParameterValues({ schema, values }) : values;
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
|
|
457
319
|
// src/agent/transport.ts
|
|
458
320
|
import { createRequire } from "module";
|
|
459
321
|
|
|
@@ -470,11 +332,7 @@ var CREDENTIAL_HEADERS = [
|
|
|
470
332
|
"x-project-id"
|
|
471
333
|
];
|
|
472
334
|
var LangWatchRedirectError = class extends Error {
|
|
473
|
-
constructor({
|
|
474
|
-
url,
|
|
475
|
-
location,
|
|
476
|
-
status
|
|
477
|
-
}) {
|
|
335
|
+
constructor({ url, location, status }) {
|
|
478
336
|
super(
|
|
479
337
|
`LangWatch refused to follow a redirect from ${url} to ${location ?? "an unknown location"} (HTTP ${status}). Set the endpoint to the final URL.`
|
|
480
338
|
);
|
|
@@ -621,7 +479,19 @@ var upgrade = async ({
|
|
|
621
479
|
const location = first.headers.get("location");
|
|
622
480
|
const refused = refusalOf({ url, response: first });
|
|
623
481
|
const target = location === null ? null : schemeUpgradeTarget({ url, location });
|
|
624
|
-
if (location === null
|
|
482
|
+
if (location === null) {
|
|
483
|
+
discard(spare);
|
|
484
|
+
throw refused;
|
|
485
|
+
}
|
|
486
|
+
if (first.status === 303) {
|
|
487
|
+
discard(spare);
|
|
488
|
+
throw refused;
|
|
489
|
+
}
|
|
490
|
+
if (target === null) {
|
|
491
|
+
discard(spare);
|
|
492
|
+
throw refused;
|
|
493
|
+
}
|
|
494
|
+
if (isStream(init?.body)) {
|
|
625
495
|
discard(spare);
|
|
626
496
|
throw refused;
|
|
627
497
|
}
|
|
@@ -671,7 +541,10 @@ function resolveTransport({
|
|
|
671
541
|
env = process.env
|
|
672
542
|
}) {
|
|
673
543
|
const candidate = isSet3(explicit) ? explicit : env.LANGWATCH_AGENT_TRANSPORT;
|
|
674
|
-
|
|
544
|
+
if (!isSet3(candidate)) return "websocket";
|
|
545
|
+
const normalized = candidate.trim().toLowerCase();
|
|
546
|
+
if (normalized === "http") return "http";
|
|
547
|
+
return "websocket";
|
|
675
548
|
}
|
|
676
549
|
var NoWebSocketError = class extends Error {
|
|
677
550
|
constructor() {
|
|
@@ -768,14 +641,14 @@ var HttpLongPollSocket = class {
|
|
|
768
641
|
return;
|
|
769
642
|
}
|
|
770
643
|
if (frame.type === "ack" && typeof frame.callId === "string") this.inFlight.add(frame.callId);
|
|
771
|
-
if (frame.type === "result" && typeof frame.callId === "string")
|
|
644
|
+
if (frame.type === "result" && typeof frame.callId === "string")
|
|
645
|
+
this.inFlight.delete(frame.callId);
|
|
772
646
|
this.outbox = this.outbox.then(() => this.registered).then(() => this.post(data)).catch(() => void 0);
|
|
773
647
|
}
|
|
774
648
|
/**
|
|
775
|
-
* Stops polling, lets
|
|
776
|
-
*
|
|
777
|
-
*
|
|
778
|
-
* hold the connection open.
|
|
649
|
+
* Stops polling, lets queued frames go out, then reports close. Bounded: on
|
|
650
|
+
* the deadline the frame requests are aborted and close is reported anyway,
|
|
651
|
+
* so an unanswering request cannot hold the connection open.
|
|
779
652
|
*/
|
|
780
653
|
close(code = 1e3) {
|
|
781
654
|
this.closed = true;
|
|
@@ -830,7 +703,8 @@ var HttpLongPollSocket = class {
|
|
|
830
703
|
signal: this.polls.signal
|
|
831
704
|
});
|
|
832
705
|
} catch (error) {
|
|
833
|
-
if (!this.closed)
|
|
706
|
+
if (!this.closed)
|
|
707
|
+
this.fail(`could not reach ${this.url}/register (${describe(error)})`, 1006);
|
|
834
708
|
return;
|
|
835
709
|
}
|
|
836
710
|
const body = await this.jsonOf(response);
|
|
@@ -851,46 +725,66 @@ var HttpLongPollSocket = class {
|
|
|
851
725
|
}
|
|
852
726
|
async pollLoop() {
|
|
853
727
|
while (!this.closed && this.token) {
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
this.fail(`the poll was answered with HTTP ${response.status}`, 1006);
|
|
880
|
-
return;
|
|
881
|
-
}
|
|
882
|
-
const frames = Array.isArray(body?.frames) ? body.frames : [];
|
|
883
|
-
for (const frame of frames) {
|
|
884
|
-
const entry = frame;
|
|
885
|
-
if (entry.type === "cancel" && typeof entry.callId === "string") this.inFlight.delete(entry.callId);
|
|
886
|
-
this.emitMessage(JSON.stringify(frame));
|
|
887
|
-
}
|
|
888
|
-
for (const listener of this.pingListeners) listener();
|
|
889
|
-
if (frames.length === 0) {
|
|
890
|
-
const elapsedMs = Date.now() - startedAt;
|
|
891
|
-
if (elapsedMs < EMPTY_POLL_FLOOR_MS) await wait(EMPTY_POLL_FLOOR_MS - elapsedMs);
|
|
892
|
-
}
|
|
728
|
+
if (!await this.pollOnce()) return;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
/** One long poll: delivers what it answered, and says whether polling goes on. */
|
|
732
|
+
async pollOnce() {
|
|
733
|
+
const query = this.inFlight.size > 0 ? `?inFlight=${encodeURIComponent([...this.inFlight].join(","))}` : "";
|
|
734
|
+
const startedAt = Date.now();
|
|
735
|
+
let response;
|
|
736
|
+
try {
|
|
737
|
+
response = await this.fetchImpl(`${this.url}/poll${query}`, {
|
|
738
|
+
method: "GET",
|
|
739
|
+
headers: this.requestHeaders(),
|
|
740
|
+
signal: this.polls.signal
|
|
741
|
+
});
|
|
742
|
+
} catch (error) {
|
|
743
|
+
if (!this.closed) this.fail(`the poll failed (${describe(error)})`, 1006);
|
|
744
|
+
return false;
|
|
745
|
+
}
|
|
746
|
+
if (this.closed) return false;
|
|
747
|
+
if (response.status === 410) {
|
|
748
|
+
this.fail(
|
|
749
|
+
"the platform no longer knows this instance, registering again",
|
|
750
|
+
SESSION_LOST_CLOSE_CODE
|
|
751
|
+
);
|
|
752
|
+
return false;
|
|
893
753
|
}
|
|
754
|
+
const body = await this.jsonOf(response);
|
|
755
|
+
if (!response.ok) {
|
|
756
|
+
this.refusePoll({ status: response.status, body });
|
|
757
|
+
return false;
|
|
758
|
+
}
|
|
759
|
+
const delivered = this.deliverFrames(body);
|
|
760
|
+
for (const listener of this.pingListeners) listener();
|
|
761
|
+
if (delivered === 0) {
|
|
762
|
+
const elapsedMs = Date.now() - startedAt;
|
|
763
|
+
if (elapsedMs < EMPTY_POLL_FLOOR_MS) await wait(EMPTY_POLL_FLOOR_MS - elapsedMs);
|
|
764
|
+
}
|
|
765
|
+
return true;
|
|
766
|
+
}
|
|
767
|
+
refusePoll({
|
|
768
|
+
status,
|
|
769
|
+
body
|
|
770
|
+
}) {
|
|
771
|
+
const answered = body && typeof body.frame === "object" && body.frame !== null ? body.frame : null;
|
|
772
|
+
if (answered) this.emitMessage(JSON.stringify(answered));
|
|
773
|
+
if (answered?.type === "refused") {
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
this.fail(`the poll was answered with HTTP ${status}`, 1006);
|
|
777
|
+
}
|
|
778
|
+
/** Emits every frame a poll answered and returns how many there were. */
|
|
779
|
+
deliverFrames(body) {
|
|
780
|
+
const frames = Array.isArray(body?.frames) ? body.frames : [];
|
|
781
|
+
for (const frame of frames) {
|
|
782
|
+
const entry = frame;
|
|
783
|
+
if (entry.type === "cancel" && typeof entry.callId === "string")
|
|
784
|
+
this.inFlight.delete(entry.callId);
|
|
785
|
+
this.emitMessage(JSON.stringify(frame));
|
|
786
|
+
}
|
|
787
|
+
return frames.length;
|
|
894
788
|
}
|
|
895
789
|
async post(data) {
|
|
896
790
|
if (!this.token) return;
|
|
@@ -908,12 +802,7 @@ var HttpLongPollSocket = class {
|
|
|
908
802
|
if (this.frames.signal.aborted) return;
|
|
909
803
|
response = null;
|
|
910
804
|
}
|
|
911
|
-
if (response
|
|
912
|
-
if (response?.status === 410) {
|
|
913
|
-
this.fail("the platform no longer knows this instance, registering again", SESSION_LOST_CLOSE_CODE);
|
|
914
|
-
return;
|
|
915
|
-
}
|
|
916
|
-
if (response && response.status < 500) return;
|
|
805
|
+
if (this.postSettled(response)) return;
|
|
917
806
|
const delay = POST_RETRY_DELAYS_MS[attempt];
|
|
918
807
|
if (delay === void 0) {
|
|
919
808
|
this.fail(`a frame could not be posted after ${attempt} retries`, 1006);
|
|
@@ -922,6 +811,18 @@ var HttpLongPollSocket = class {
|
|
|
922
811
|
await wait(delay);
|
|
923
812
|
}
|
|
924
813
|
}
|
|
814
|
+
/** Whether a post's response ends its retries: delivered, refused, or the session lost. */
|
|
815
|
+
postSettled(response) {
|
|
816
|
+
if (response?.ok) return true;
|
|
817
|
+
if (response?.status === 410) {
|
|
818
|
+
this.fail(
|
|
819
|
+
"the platform no longer knows this instance, registering again",
|
|
820
|
+
SESSION_LOST_CLOSE_CODE
|
|
821
|
+
);
|
|
822
|
+
return true;
|
|
823
|
+
}
|
|
824
|
+
return response !== null && response.status < 500;
|
|
825
|
+
}
|
|
925
826
|
async jsonOf(response) {
|
|
926
827
|
try {
|
|
927
828
|
const parsed = await response.json();
|
|
@@ -975,19 +876,257 @@ function openTransportSocket({
|
|
|
975
876
|
return transport === "http" ? new HttpLongPollSocket({ url: httpUrl, headers }) : socketFactory({ url: websocketUrl, headers });
|
|
976
877
|
}
|
|
977
878
|
|
|
879
|
+
// src/agent/schema.ts
|
|
880
|
+
var AgentParameterError = class extends Error {
|
|
881
|
+
constructor(message) {
|
|
882
|
+
super(message);
|
|
883
|
+
this.code = "agent_parameter_invalid";
|
|
884
|
+
this.name = "AgentParameterError";
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
var ACCEPTED_FORMS = `parameters must be a definition map ({ model: { options: [...], default: '...' } }), a Standard JSON Schema object (one with "~standard".jsonSchema), or a JSON Schema object ({ type: 'object', properties })`;
|
|
888
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
889
|
+
var isStandardJsonSchema = (value) => {
|
|
890
|
+
if (!isRecord2(value)) return false;
|
|
891
|
+
const standard = value["~standard"];
|
|
892
|
+
return isRecord2(standard) && isRecord2(standard.jsonSchema);
|
|
893
|
+
};
|
|
894
|
+
var isJsonSchemaObject = (value) => isRecord2(value) && value.type === "object" && isRecord2(value.properties);
|
|
895
|
+
var isDefinitionMap = (value) => isRecord2(value) && Object.values(value).every(isRecord2);
|
|
896
|
+
var definitionType = (definition) => {
|
|
897
|
+
if (definition.type) return definition.type;
|
|
898
|
+
if (definition.options) return "string";
|
|
899
|
+
const value = definition.default;
|
|
900
|
+
if (typeof value === "number") return "number";
|
|
901
|
+
if (typeof value === "boolean") return "boolean";
|
|
902
|
+
return "string";
|
|
903
|
+
};
|
|
904
|
+
var definitionMapToSchema = (definitions) => {
|
|
905
|
+
const properties = {};
|
|
906
|
+
const required = [];
|
|
907
|
+
for (const [name, definition] of Object.entries(definitions)) {
|
|
908
|
+
const property = { type: definitionType(definition) };
|
|
909
|
+
if (definition.options) property.enum = [...definition.options];
|
|
910
|
+
if (definition.default !== void 0) property.default = definition.default;
|
|
911
|
+
else required.push(name);
|
|
912
|
+
if (definition.description !== void 0) property.description = definition.description;
|
|
913
|
+
properties[name] = property;
|
|
914
|
+
}
|
|
915
|
+
const schema = { type: "object", properties };
|
|
916
|
+
if (required.length > 0) schema.required = required;
|
|
917
|
+
return schema;
|
|
918
|
+
};
|
|
919
|
+
var readStandardJsonSchema = (input) => {
|
|
920
|
+
const converter = input["~standard"].jsonSchema;
|
|
921
|
+
const options = { target: "draft-2020-12" };
|
|
922
|
+
const schema = converter.input?.(options) ?? converter.output?.(options);
|
|
923
|
+
if (schema === void 0) {
|
|
924
|
+
throw new AgentParameterError(
|
|
925
|
+
`the "~standard".jsonSchema converter has no input function; ${ACCEPTED_FORMS}`
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
if (!isRecord2(schema)) {
|
|
929
|
+
throw new AgentParameterError(
|
|
930
|
+
`the "~standard".jsonSchema converter returned no object; ${ACCEPTED_FORMS}`
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
return schema;
|
|
934
|
+
};
|
|
935
|
+
function toParameterSchema(input) {
|
|
936
|
+
if (input === void 0) return { type: "object", properties: {} };
|
|
937
|
+
if (isStandardJsonSchema(input)) return readStandardJsonSchema(input);
|
|
938
|
+
if (isJsonSchemaObject(input)) return input;
|
|
939
|
+
if (isDefinitionMap(input)) return definitionMapToSchema(input);
|
|
940
|
+
throw new AgentParameterError(ACCEPTED_FORMS);
|
|
941
|
+
}
|
|
942
|
+
var specType = (property) => {
|
|
943
|
+
const type = Array.isArray(property.type) ? property.type.find((item) => item !== "null") : property.type;
|
|
944
|
+
if (type === "number" || type === "integer") return "number";
|
|
945
|
+
if (type === "boolean") return "boolean";
|
|
946
|
+
return "string";
|
|
947
|
+
};
|
|
948
|
+
var scalar = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : void 0;
|
|
949
|
+
function parameterSpecsFromSchema(schema) {
|
|
950
|
+
const properties = isRecord2(schema.properties) ? schema.properties : {};
|
|
951
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
952
|
+
const specs = [];
|
|
953
|
+
for (const [name, raw] of Object.entries(properties)) {
|
|
954
|
+
const property = isRecord2(raw) ? raw : {};
|
|
955
|
+
const spec = { name, type: specType(property) };
|
|
956
|
+
const options = Array.isArray(property.enum) ? property.enum.filter((item) => typeof item === "string") : void 0;
|
|
957
|
+
if (options && options.length > 0) spec.options = options;
|
|
958
|
+
const fallback = scalar(property.default);
|
|
959
|
+
if (fallback !== void 0) spec.default = fallback;
|
|
960
|
+
if (typeof property.description === "string") spec.description = property.description;
|
|
961
|
+
spec.required = fallback === void 0 && required.has(name);
|
|
962
|
+
specs.push(spec);
|
|
963
|
+
}
|
|
964
|
+
return specs;
|
|
965
|
+
}
|
|
966
|
+
var coerce = ({
|
|
967
|
+
spec,
|
|
968
|
+
value
|
|
969
|
+
}) => {
|
|
970
|
+
if (spec.type === "number") {
|
|
971
|
+
return coerceNumber(spec.name, value);
|
|
972
|
+
}
|
|
973
|
+
if (spec.type === "boolean") {
|
|
974
|
+
if (typeof value === "boolean") return value;
|
|
975
|
+
if (value === "true") return true;
|
|
976
|
+
if (value === "false") return false;
|
|
977
|
+
throw new AgentParameterError(
|
|
978
|
+
`parameter "${spec.name}" must be true or false, got ${JSON.stringify(value)}`
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
const asString = typeof value === "string" ? value : String(value);
|
|
982
|
+
if (spec.options && !spec.options.includes(asString)) {
|
|
983
|
+
throw new AgentParameterError(
|
|
984
|
+
`parameter "${spec.name}" must be one of ${spec.options.join(", ")}, got ${JSON.stringify(value)}`
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
return asString;
|
|
988
|
+
};
|
|
989
|
+
function resolveParameterValues({
|
|
990
|
+
specs,
|
|
991
|
+
supplied
|
|
992
|
+
}) {
|
|
993
|
+
const values = { ...supplied };
|
|
994
|
+
for (const spec of specs) {
|
|
995
|
+
const value = values[spec.name];
|
|
996
|
+
if (value === void 0) {
|
|
997
|
+
if (spec.default !== void 0) {
|
|
998
|
+
values[spec.name] = spec.default;
|
|
999
|
+
continue;
|
|
1000
|
+
}
|
|
1001
|
+
if (!spec.required) continue;
|
|
1002
|
+
throw new AgentParameterError(
|
|
1003
|
+
`parameter "${spec.name}" is required and the run did not supply it`
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
values[spec.name] = coerce({ spec, value });
|
|
1007
|
+
}
|
|
1008
|
+
return values;
|
|
1009
|
+
}
|
|
1010
|
+
var issuePath = (issue) => (issue.path ?? []).map(
|
|
1011
|
+
(segment) => typeof segment === "object" && segment !== null ? String(segment.key) : String(segment)
|
|
1012
|
+
).join(".");
|
|
1013
|
+
async function validateParameterValues({
|
|
1014
|
+
schema,
|
|
1015
|
+
values
|
|
1016
|
+
}) {
|
|
1017
|
+
const standard = schema["~standard"];
|
|
1018
|
+
const result = await standard.validate?.(values);
|
|
1019
|
+
if (result === void 0) return values;
|
|
1020
|
+
if (result.issues) {
|
|
1021
|
+
const detail = result.issues.map((issue) => {
|
|
1022
|
+
const path2 = issuePath(issue);
|
|
1023
|
+
return path2 ? `${path2}: ${issue.message}` : issue.message;
|
|
1024
|
+
}).join("; ");
|
|
1025
|
+
throw new AgentParameterError(`parameters refused by the schema: ${detail}`);
|
|
1026
|
+
}
|
|
1027
|
+
const parsed = isRecord2(result.value) ? result.value : {};
|
|
1028
|
+
return { ...values, ...parsed };
|
|
1029
|
+
}
|
|
1030
|
+
function createParameterReader({
|
|
1031
|
+
input,
|
|
1032
|
+
specs
|
|
1033
|
+
}) {
|
|
1034
|
+
const schema = isStandardJsonSchema(input) ? input : void 0;
|
|
1035
|
+
return async (supplied) => {
|
|
1036
|
+
const values = resolveParameterValues({ specs, supplied });
|
|
1037
|
+
return schema ? validateParameterValues({ schema, values }) : values;
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
function coerceNumber(name, value) {
|
|
1041
|
+
const asNumber = typeof value === "number" ? value : Number(value);
|
|
1042
|
+
if (typeof value === "boolean") {
|
|
1043
|
+
throw new AgentParameterError(
|
|
1044
|
+
`parameter "${name}" must be a number, got ${JSON.stringify(value)}`
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
if (!Number.isFinite(asNumber)) {
|
|
1048
|
+
throw new AgentParameterError(
|
|
1049
|
+
`parameter "${name}" must be a number, got ${JSON.stringify(value)}`
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
const stringValue2 = String(value).trim();
|
|
1053
|
+
if (stringValue2 === "") {
|
|
1054
|
+
throw new AgentParameterError(
|
|
1055
|
+
`parameter "${name}" must be a number, got ${JSON.stringify(value)}`
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
return asNumber;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// src/agent/state.ts
|
|
1062
|
+
var STATE_MAX_BYTES = 64 * 1024;
|
|
1063
|
+
var AgentStateError = class extends Error {
|
|
1064
|
+
constructor(code, message) {
|
|
1065
|
+
super(message);
|
|
1066
|
+
this.code = code;
|
|
1067
|
+
this.name = "AgentStateError";
|
|
1068
|
+
}
|
|
1069
|
+
};
|
|
1070
|
+
var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1071
|
+
function checkState(value) {
|
|
1072
|
+
if (value === void 0 || value === null) return void 0;
|
|
1073
|
+
if (!isPlainObject(value)) {
|
|
1074
|
+
throw new AgentStateError(
|
|
1075
|
+
"agent_call_failed",
|
|
1076
|
+
`the state must be a JSON object, got ${Array.isArray(value) ? "an array" : typeof value}`
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
let encoded;
|
|
1080
|
+
try {
|
|
1081
|
+
encoded = JSON.stringify(value);
|
|
1082
|
+
} catch (error) {
|
|
1083
|
+
throw new AgentStateError(
|
|
1084
|
+
"agent_call_failed",
|
|
1085
|
+
`the state is not JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
1088
|
+
const sizeBytes = Buffer.byteLength(encoded, "utf8");
|
|
1089
|
+
if (sizeBytes > STATE_MAX_BYTES) {
|
|
1090
|
+
throw new AgentStateError(
|
|
1091
|
+
"agent_payload_too_large",
|
|
1092
|
+
`The state is ${sizeBytes} bytes, above the limit of ${STATE_MAX_BYTES} bytes.`
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
return value;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
978
1098
|
// src/agent/client.ts
|
|
1099
|
+
var kindRefusal = (runtime, kind) => {
|
|
1100
|
+
if (kind === "turn") return null;
|
|
1101
|
+
if (kind !== "setup" && kind !== "teardown") {
|
|
1102
|
+
return { code: "agent_call_failed", message: `unknown call kind "${kind}"` };
|
|
1103
|
+
}
|
|
1104
|
+
if (runtime.hooks[kind]) return null;
|
|
1105
|
+
return {
|
|
1106
|
+
code: "hook_not_declared",
|
|
1107
|
+
message: `agent "${runtime.name}" declares no ${kind} hook`
|
|
1108
|
+
};
|
|
1109
|
+
};
|
|
1110
|
+
var errorCodeOf = (error) => error instanceof AgentParameterError || error instanceof AgentStateError ? error.code : "agent_call_failed";
|
|
1111
|
+
var answerCall = async ({
|
|
1112
|
+
runtime,
|
|
1113
|
+
frame,
|
|
1114
|
+
call
|
|
1115
|
+
}) => {
|
|
1116
|
+
const hook = frame.kind === "setup" || frame.kind === "teardown" ? runtime.hooks[frame.kind] : void 0;
|
|
1117
|
+
if (!hook) {
|
|
1118
|
+
const result = await runtime.run(call);
|
|
1119
|
+
return { ...result, state: checkState(result.state) };
|
|
1120
|
+
}
|
|
1121
|
+
const state = await hook({ run: frame.run, state: frame.state, params: call.params });
|
|
1122
|
+
return { state: checkState(frame.kind === "setup" ? state ?? {} : state) };
|
|
1123
|
+
};
|
|
979
1124
|
var FAILURE_NOTICE_INTERVAL_MS = 5 * 6e4;
|
|
980
1125
|
var NOT_CONNECTED = "not connected to LangWatch";
|
|
981
1126
|
function refusalAdvice(frame) {
|
|
982
1127
|
switch (frame.code) {
|
|
983
1128
|
case "project_required": {
|
|
984
|
-
const
|
|
985
|
-
const listed = projects.map((project) => {
|
|
986
|
-
const entry = project;
|
|
987
|
-
const id = typeof entry.id === "string" ? entry.id : "";
|
|
988
|
-
const name = typeof entry.name === "string" ? entry.name : "";
|
|
989
|
-
return name && id ? `${name} (${id})` : id || name;
|
|
990
|
-
}).filter((line) => line !== "");
|
|
1129
|
+
const listed = projectNames(frame.meta?.projects);
|
|
991
1130
|
return `the API key reaches more than one project. Set LANGWATCH_PROJECT_ID to one of: ${listed.length > 0 ? listed.join(", ") : "the projects the key reaches"}.`;
|
|
992
1131
|
}
|
|
993
1132
|
case "api_key_invalid":
|
|
@@ -1007,8 +1146,30 @@ function refusalAdvice(frame) {
|
|
|
1007
1146
|
return `${frame.message} (${frame.code})`;
|
|
1008
1147
|
}
|
|
1009
1148
|
}
|
|
1149
|
+
function projectNames(value) {
|
|
1150
|
+
if (!Array.isArray(value)) return [];
|
|
1151
|
+
return value.map(projectName).filter((name) => name !== "");
|
|
1152
|
+
}
|
|
1153
|
+
function projectName(value) {
|
|
1154
|
+
if (!isRecord3(value)) return "";
|
|
1155
|
+
const id = stringValue(value.id);
|
|
1156
|
+
const name = stringValue(value.name);
|
|
1157
|
+
if (name && id) return `${name} (${id})`;
|
|
1158
|
+
return id || name;
|
|
1159
|
+
}
|
|
1160
|
+
function isRecord3(value) {
|
|
1161
|
+
return typeof value === "object" && value !== null;
|
|
1162
|
+
}
|
|
1163
|
+
function stringValue(value) {
|
|
1164
|
+
return typeof value === "string" ? value : "";
|
|
1165
|
+
}
|
|
1010
1166
|
var SHUTDOWN_SIGNALS = ["SIGINT", "SIGTERM"];
|
|
1011
1167
|
var CLOSE_GRACE_MS = 500;
|
|
1168
|
+
var readCallTraceContext = (frame) => {
|
|
1169
|
+
const parent = frame.traceparent ? propagation.extract(context.active(), { traceparent: frame.traceparent }) : context.active();
|
|
1170
|
+
const traceId = trace.getSpanContext(parent)?.traceId ?? traceIdFromTraceparent(frame.traceparent) ?? "";
|
|
1171
|
+
return { parent, traceId };
|
|
1172
|
+
};
|
|
1012
1173
|
var AgentClient = class {
|
|
1013
1174
|
constructor(config) {
|
|
1014
1175
|
this.agents = [];
|
|
@@ -1049,7 +1210,7 @@ var AgentClient = class {
|
|
|
1049
1210
|
get isRegistered() {
|
|
1050
1211
|
return this.registered;
|
|
1051
1212
|
}
|
|
1052
|
-
/** True once the client gave up: refused, or no socket implementation
|
|
1213
|
+
/** True once the client gave up: refused, or no socket implementation available. */
|
|
1053
1214
|
get isStopped() {
|
|
1054
1215
|
return this.stopped;
|
|
1055
1216
|
}
|
|
@@ -1066,7 +1227,9 @@ var AgentClient = class {
|
|
|
1066
1227
|
addAgent(runtime) {
|
|
1067
1228
|
this.agents.push(runtime);
|
|
1068
1229
|
if (this.gaveUp) {
|
|
1069
|
-
this.logger.debug(
|
|
1230
|
+
this.logger.debug(
|
|
1231
|
+
`agent "${runtime.name}" ${NOT_CONNECTED}: the connection gave up earlier in this process`
|
|
1232
|
+
);
|
|
1070
1233
|
return;
|
|
1071
1234
|
}
|
|
1072
1235
|
this.stopped = false;
|
|
@@ -1076,6 +1239,10 @@ var AgentClient = class {
|
|
|
1076
1239
|
}
|
|
1077
1240
|
this.scheduleConnect(0);
|
|
1078
1241
|
}
|
|
1242
|
+
/** Registers the agents again after one changed: a hook declared once the socket opened. */
|
|
1243
|
+
refreshAgents() {
|
|
1244
|
+
if (this.socket) this.restartSocket();
|
|
1245
|
+
}
|
|
1079
1246
|
/** Closes the socket and connects again at once, keeping the reconnect loop. */
|
|
1080
1247
|
restartSocket() {
|
|
1081
1248
|
const socket = this.socket;
|
|
@@ -1235,7 +1402,10 @@ var AgentClient = class {
|
|
|
1235
1402
|
* connection is lost, and silence while the retries run: the same notice
|
|
1236
1403
|
* repeats only after the notice interval, and a reconnect resets it.
|
|
1237
1404
|
*/
|
|
1238
|
-
noteDisconnected({
|
|
1405
|
+
noteDisconnected({
|
|
1406
|
+
wasRegistered,
|
|
1407
|
+
code
|
|
1408
|
+
}) {
|
|
1239
1409
|
const now = Date.now();
|
|
1240
1410
|
if (wasRegistered) {
|
|
1241
1411
|
this.logger.warn(
|
|
@@ -1328,7 +1498,8 @@ var AgentClient = class {
|
|
|
1328
1498
|
this.logger.info(`connected to LangWatch over HTTP long polling at ${this.httpUrl}`);
|
|
1329
1499
|
}
|
|
1330
1500
|
this.heartbeatIntervalMs = frame.heartbeatIntervalMs;
|
|
1331
|
-
if (frame.instanceId && frame.instanceId !== this.instance.id)
|
|
1501
|
+
if (frame.instanceId && frame.instanceId !== this.instance.id)
|
|
1502
|
+
this.instance.id = frame.instanceId;
|
|
1332
1503
|
this.byId.clear();
|
|
1333
1504
|
for (const entry of frame.agents) {
|
|
1334
1505
|
const runtime = this.agents.find(
|
|
@@ -1355,21 +1526,9 @@ var AgentClient = class {
|
|
|
1355
1526
|
});
|
|
1356
1527
|
return;
|
|
1357
1528
|
}
|
|
1358
|
-
const
|
|
1359
|
-
if (
|
|
1360
|
-
this.sendError({
|
|
1361
|
-
callId: frame.callId,
|
|
1362
|
-
code: "agent_busy",
|
|
1363
|
-
message: `agent "${runtime.name}" has ${busy} call${busy === 1 ? "" : "s"} in flight, its limit`
|
|
1364
|
-
});
|
|
1365
|
-
return;
|
|
1366
|
-
}
|
|
1367
|
-
if (frame.deadlineAt !== null && frame.deadlineAt <= Date.now()) {
|
|
1368
|
-
this.sendError({
|
|
1369
|
-
callId: frame.callId,
|
|
1370
|
-
code: "agent_call_timeout",
|
|
1371
|
-
message: "the call deadline passed before it started"
|
|
1372
|
-
});
|
|
1529
|
+
const refusal = this.callRefusal({ frame, runtime });
|
|
1530
|
+
if (refusal) {
|
|
1531
|
+
this.sendError({ callId: frame.callId, ...refusal });
|
|
1373
1532
|
return;
|
|
1374
1533
|
}
|
|
1375
1534
|
const entry = { runtime, cancelled: false, timer: null };
|
|
@@ -1392,18 +1551,18 @@ var AgentClient = class {
|
|
|
1392
1551
|
}
|
|
1393
1552
|
this.send({ type: "ack", protocol: PROTOCOL_VERSION, callId: frame.callId });
|
|
1394
1553
|
this.armCallDeadline({ frame, entry, runtime });
|
|
1395
|
-
const parent
|
|
1396
|
-
const traceId = trace.getSpanContext(parent)?.traceId ?? traceIdFromTraceparent(frame.traceparent) ?? "";
|
|
1554
|
+
const { parent, traceId } = readCallTraceContext(frame);
|
|
1397
1555
|
const call = {
|
|
1398
1556
|
messages: frame.messages,
|
|
1399
1557
|
newMessages: frame.newMessages,
|
|
1400
1558
|
threadId: frame.threadId,
|
|
1401
1559
|
session: frame.session,
|
|
1560
|
+
state: frame.state,
|
|
1402
1561
|
params,
|
|
1403
1562
|
traceId
|
|
1404
1563
|
};
|
|
1405
1564
|
try {
|
|
1406
|
-
const result = await context.with(parent, () => runtime
|
|
1565
|
+
const result = await context.with(parent, () => answerCall({ runtime, frame, call }));
|
|
1407
1566
|
if (entry.cancelled) return;
|
|
1408
1567
|
this.releaseCall({ callId: frame.callId, entry });
|
|
1409
1568
|
await this.flushSpans();
|
|
@@ -1411,12 +1570,13 @@ var AgentClient = class {
|
|
|
1411
1570
|
type: "result",
|
|
1412
1571
|
protocol: PROTOCOL_VERSION,
|
|
1413
1572
|
callId: frame.callId,
|
|
1414
|
-
output: result.output,
|
|
1415
|
-
...result.session === void 0 ? {} : { session: result.session }
|
|
1573
|
+
...result.output === void 0 ? {} : { output: result.output },
|
|
1574
|
+
...result.session === void 0 ? {} : { session: result.session },
|
|
1575
|
+
...result.state === void 0 ? {} : { state: result.state }
|
|
1416
1576
|
});
|
|
1417
1577
|
} catch (error) {
|
|
1418
1578
|
if (entry.cancelled) return;
|
|
1419
|
-
const code = error
|
|
1579
|
+
const code = errorCodeOf(error);
|
|
1420
1580
|
const message = describeError(error);
|
|
1421
1581
|
this.logger.warn(`agent "${runtime.name}" call ${frame.callId} failed: ${message}`);
|
|
1422
1582
|
this.releaseCall({ callId: frame.callId, entry });
|
|
@@ -1426,15 +1586,32 @@ var AgentClient = class {
|
|
|
1426
1586
|
this.releaseCall({ callId: frame.callId, entry });
|
|
1427
1587
|
}
|
|
1428
1588
|
}
|
|
1589
|
+
/** Why the runtime cannot take this call now, or null when it can. */
|
|
1590
|
+
callRefusal({
|
|
1591
|
+
frame,
|
|
1592
|
+
runtime
|
|
1593
|
+
}) {
|
|
1594
|
+
const kind = kindRefusal(runtime, frame.kind);
|
|
1595
|
+
if (kind) return kind;
|
|
1596
|
+
const busy = [...this.inFlight.values()].filter((call) => call.runtime === runtime).length;
|
|
1597
|
+
if (busy >= runtime.concurrency) {
|
|
1598
|
+
return {
|
|
1599
|
+
code: "agent_busy",
|
|
1600
|
+
message: `agent "${runtime.name}" has ${busy} call${busy === 1 ? "" : "s"} in flight, its limit`
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
if (frame.deadlineAt !== null && frame.deadlineAt <= Date.now()) {
|
|
1604
|
+
return {
|
|
1605
|
+
code: "agent_call_timeout",
|
|
1606
|
+
message: "the call deadline passed before it started"
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
return null;
|
|
1610
|
+
}
|
|
1429
1611
|
/**
|
|
1430
|
-
* Exports the spans of the call now
|
|
1431
|
-
*
|
|
1432
|
-
*
|
|
1433
|
-
* made the judge report the spans missing.
|
|
1434
|
-
*
|
|
1435
|
-
* The call awaits this before it sends its result or its error: the frame is
|
|
1436
|
-
* what tells the platform the turn is over, so a frame that goes out first
|
|
1437
|
-
* lets the judge read the call while its spans are still in the exporter.
|
|
1612
|
+
* Exports the spans of the call now, since a batch exporter would
|
|
1613
|
+
* otherwise hold them for seconds after the judge reads them. Awaited
|
|
1614
|
+
* before the result or error frame goes out.
|
|
1438
1615
|
*/
|
|
1439
1616
|
async flushSpans() {
|
|
1440
1617
|
const provider = trace.getTracerProvider();
|
|
@@ -1466,26 +1643,33 @@ var AgentClient = class {
|
|
|
1466
1643
|
runtime
|
|
1467
1644
|
}) {
|
|
1468
1645
|
const fromDeadline = frame.deadlineAt === null ? Infinity : frame.deadlineAt - Date.now();
|
|
1469
|
-
const limit = Math.min(fromDeadline, runtime.timeoutMs);
|
|
1646
|
+
const limit = frame.kind !== "turn" && frame.deadlineAt !== null ? fromDeadline : Math.min(fromDeadline, runtime.timeoutMs);
|
|
1470
1647
|
if (!Number.isFinite(limit)) return;
|
|
1471
|
-
const timer = setTimeout(
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1648
|
+
const timer = setTimeout(
|
|
1649
|
+
() => {
|
|
1650
|
+
entry.timer = null;
|
|
1651
|
+
if (entry.cancelled) return;
|
|
1652
|
+
entry.cancelled = true;
|
|
1653
|
+
this.releaseCall({ callId: frame.callId, entry });
|
|
1654
|
+
this.logger.warn(
|
|
1655
|
+
`agent "${runtime.name}" call ${frame.callId} passed its ${limit} ms limit`
|
|
1656
|
+
);
|
|
1657
|
+
this.sendError({
|
|
1658
|
+
callId: frame.callId,
|
|
1659
|
+
code: "agent_call_timeout",
|
|
1660
|
+
message: `the call passed the ${limit} ms limit of agent "${runtime.name}"`
|
|
1661
|
+
});
|
|
1662
|
+
},
|
|
1663
|
+
Math.max(0, limit)
|
|
1664
|
+
);
|
|
1485
1665
|
timer.unref();
|
|
1486
1666
|
entry.timer = timer;
|
|
1487
1667
|
}
|
|
1488
|
-
sendError({
|
|
1668
|
+
sendError({
|
|
1669
|
+
callId,
|
|
1670
|
+
code,
|
|
1671
|
+
message
|
|
1672
|
+
}) {
|
|
1489
1673
|
this.send({ type: "result", protocol: PROTOCOL_VERSION, callId, error: { code, message } });
|
|
1490
1674
|
}
|
|
1491
1675
|
guard(action) {
|
|
@@ -1528,7 +1712,11 @@ var installShutdownHooks = () => {
|
|
|
1528
1712
|
}
|
|
1529
1713
|
process.on("beforeExit", onBeforeExit);
|
|
1530
1714
|
};
|
|
1531
|
-
function warnOnce2({
|
|
1715
|
+
function warnOnce2({
|
|
1716
|
+
logger,
|
|
1717
|
+
key,
|
|
1718
|
+
message
|
|
1719
|
+
}) {
|
|
1532
1720
|
if (noticesGiven.has(key)) {
|
|
1533
1721
|
logger.debug(message);
|
|
1534
1722
|
return;
|
|
@@ -1571,22 +1759,33 @@ var DEFAULT_CONCURRENCY = 10;
|
|
|
1571
1759
|
var isMessage = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.role === "string";
|
|
1572
1760
|
function normalizeReply(reply) {
|
|
1573
1761
|
if (typeof reply === "string") return { output: reply };
|
|
1574
|
-
if (Array.isArray(reply)
|
|
1762
|
+
if (Array.isArray(reply)) {
|
|
1763
|
+
if (reply.every(isMessage)) return { output: reply };
|
|
1764
|
+
}
|
|
1575
1765
|
if (isMessage(reply)) return { output: reply };
|
|
1576
1766
|
if (typeof reply === "object" && reply !== null && "output" in reply) {
|
|
1577
|
-
const { output, session } = reply;
|
|
1578
|
-
|
|
1579
|
-
|
|
1767
|
+
const { output, session, state } = reply;
|
|
1768
|
+
return {
|
|
1769
|
+
output: normalizeReply(output).output,
|
|
1770
|
+
...session === void 0 ? {} : { session },
|
|
1771
|
+
...state === void 0 ? {} : { state }
|
|
1772
|
+
};
|
|
1580
1773
|
}
|
|
1581
1774
|
throw new Error(
|
|
1582
|
-
"the agent handler must return a string, a message, a list of messages, or { output, session }"
|
|
1775
|
+
"the agent handler must return a string, a message, a list of messages, or { output, session, state }"
|
|
1583
1776
|
);
|
|
1584
1777
|
}
|
|
1585
|
-
var clampTimeout = ({
|
|
1778
|
+
var clampTimeout = ({
|
|
1779
|
+
timeoutMs,
|
|
1780
|
+
logger,
|
|
1781
|
+
name
|
|
1782
|
+
}) => {
|
|
1586
1783
|
if (timeoutMs === void 0) return DEFAULT_TIMEOUT_MS;
|
|
1587
1784
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return DEFAULT_TIMEOUT_MS;
|
|
1588
1785
|
if (timeoutMs > MAX_TIMEOUT_MS) {
|
|
1589
|
-
logger.warn(
|
|
1786
|
+
logger.warn(
|
|
1787
|
+
`agent "${name}": timeoutMs ${timeoutMs} is above the ${MAX_TIMEOUT_MS} cap, using the cap`
|
|
1788
|
+
);
|
|
1590
1789
|
return MAX_TIMEOUT_MS;
|
|
1591
1790
|
}
|
|
1592
1791
|
return Math.floor(timeoutMs);
|
|
@@ -1617,10 +1816,12 @@ function connectAgent(options, handler) {
|
|
|
1617
1816
|
newMessages: call.newMessages ?? call.messages,
|
|
1618
1817
|
threadId: call.threadId ?? `local_${Date.now().toString(36)}`,
|
|
1619
1818
|
session: call.session ?? null,
|
|
1819
|
+
state: call.state ?? null,
|
|
1620
1820
|
params,
|
|
1621
1821
|
traceId: call.traceId ?? ""
|
|
1622
1822
|
});
|
|
1623
1823
|
};
|
|
1824
|
+
const hooks = {};
|
|
1624
1825
|
const runtime = {
|
|
1625
1826
|
name,
|
|
1626
1827
|
environment,
|
|
@@ -1630,14 +1831,29 @@ function connectAgent(options, handler) {
|
|
|
1630
1831
|
parameters,
|
|
1631
1832
|
concurrency,
|
|
1632
1833
|
timeoutMs,
|
|
1633
|
-
...options.sticky ? { sticky: true } : {}
|
|
1834
|
+
...options.sticky ? { sticky: true } : {},
|
|
1835
|
+
hooks: { setup: false, teardown: false }
|
|
1634
1836
|
},
|
|
1635
1837
|
readParams: readParams2,
|
|
1636
1838
|
concurrency,
|
|
1637
1839
|
timeoutMs,
|
|
1638
|
-
run: runHandler
|
|
1840
|
+
run: runHandler,
|
|
1841
|
+
hooks
|
|
1639
1842
|
};
|
|
1640
1843
|
let detach;
|
|
1844
|
+
let refresh;
|
|
1845
|
+
const scenario = {
|
|
1846
|
+
setup: (hook) => {
|
|
1847
|
+
hooks.setup = async ({ run, params }) => hook(run, params);
|
|
1848
|
+
runtime.register.hooks = { setup: true, teardown: Boolean(hooks.teardown) };
|
|
1849
|
+
refresh?.();
|
|
1850
|
+
},
|
|
1851
|
+
teardown: (hook) => {
|
|
1852
|
+
hooks.teardown = async ({ run, state, params }) => hook(run, state, params);
|
|
1853
|
+
runtime.register.hooks = { setup: Boolean(hooks.setup), teardown: true };
|
|
1854
|
+
refresh?.();
|
|
1855
|
+
}
|
|
1856
|
+
};
|
|
1641
1857
|
if (!resolveEnabled({ explicit: options.enabled })) {
|
|
1642
1858
|
logger.debug(`agent "${name}" not connected to LangWatch: the connection is disabled`);
|
|
1643
1859
|
} else {
|
|
@@ -1659,15 +1875,18 @@ function connectAgent(options, handler) {
|
|
|
1659
1875
|
});
|
|
1660
1876
|
client.addAgent(runtime);
|
|
1661
1877
|
detach = () => client.removeAgent(runtime);
|
|
1878
|
+
refresh = () => client.refreshAgents();
|
|
1662
1879
|
}
|
|
1663
1880
|
}
|
|
1664
1881
|
const connected = Object.assign(invoke, {
|
|
1665
1882
|
environment,
|
|
1666
1883
|
parameters,
|
|
1884
|
+
scenario,
|
|
1667
1885
|
disconnect: async () => {
|
|
1668
1886
|
if (!detach) return;
|
|
1669
1887
|
const release = detach;
|
|
1670
1888
|
detach = void 0;
|
|
1889
|
+
refresh = void 0;
|
|
1671
1890
|
await release();
|
|
1672
1891
|
}
|
|
1673
1892
|
});
|
|
@@ -1677,9 +1896,11 @@ function connectAgent(options, handler) {
|
|
|
1677
1896
|
export {
|
|
1678
1897
|
AGENT_TRANSPORTS,
|
|
1679
1898
|
AgentParameterError,
|
|
1899
|
+
AgentStateError,
|
|
1680
1900
|
DEFAULT_TIMEOUT_MS,
|
|
1681
1901
|
MAX_TIMEOUT_MS,
|
|
1682
1902
|
PROTOCOL_VERSION,
|
|
1903
|
+
STATE_MAX_BYTES,
|
|
1683
1904
|
connectAgent,
|
|
1684
1905
|
normalizeReply,
|
|
1685
1906
|
parameterSpecsFromSchema,
|