langwatch 1.19.0 → 2.0.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 +27 -13
- package/dist/agent/index.d.mts +122 -79
- package/dist/agent/index.d.ts +122 -79
- package/dist/agent/index.js +541 -301
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/index.mjs +540 -302
- package/dist/agent/index.mjs.map +1 -1
- package/dist/{chunk-NPEK4RSD.mjs → chunk-2GKEMCKT.mjs} +7757 -2409
- package/dist/chunk-2GKEMCKT.mjs.map +1 -0
- package/dist/chunk-5VYEG3RS.js +117 -0
- package/dist/chunk-5VYEG3RS.js.map +1 -0
- package/dist/chunk-AYPQNLCC.mjs +852 -0
- package/dist/chunk-AYPQNLCC.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-DLZCOUXC.js +852 -0
- package/dist/chunk-DLZCOUXC.js.map +1 -0
- package/dist/{chunk-O4J3ZPZY.js → chunk-F7BVJW3S.js} +7912 -2564
- package/dist/chunk-F7BVJW3S.js.map +1 -0
- package/dist/{chunk-SY5HSL5G.mjs → chunk-M3DSPR5S.mjs} +124 -112
- package/dist/chunk-M3DSPR5S.mjs.map +1 -0
- package/dist/{chunk-CSV7B7B5.js → chunk-MAO3SIBB.js} +141 -129
- package/dist/chunk-MAO3SIBB.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 +853 -552
- package/dist/implementation-BXVPzwOt.d.mts +261 -0
- package/dist/implementation-DJ-JveZl.d.ts +261 -0
- package/dist/index.d.mts +479 -1130
- package/dist/index.d.ts +479 -1130
- package/dist/index.js +7908 -3525
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +7883 -3500
- 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 +13 -10
- 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--ExXvd26.d.mts +16583 -0
- package/dist/{types-VOZv9LYO.d.ts → types-CoS5PmeU.d.mts} +22 -59
- package/dist/{types-VOZv9LYO.d.mts → types-CoS5PmeU.d.ts} +22 -59
- package/dist/types-CpT3iEIj.d.ts +16583 -0
- 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 +94 -64
- package/dist/chunk-57L2K66L.mjs.map +0 -1
- package/dist/chunk-73QOHSHU.js.map +0 -1
- package/dist/chunk-CSV7B7B5.js.map +0 -1
- package/dist/chunk-LCG5Y2EG.js.map +0 -1
- package/dist/chunk-NPEK4RSD.mjs.map +0 -1
- package/dist/chunk-O4J3ZPZY.js.map +0 -1
- package/dist/chunk-PIYOJF6V.mjs +0 -566
- package/dist/chunk-PIYOJF6V.mjs.map +0 -1
- package/dist/chunk-SY5HSL5G.mjs.map +0 -1
- package/dist/chunk-WGLVIV4J.js +0 -566
- package/dist/chunk-WGLVIV4J.js.map +0 -1
- package/dist/chunk-WMDQVWA6.mjs.map +0 -1
- package/dist/implementation-CeTvnq6e.d.mts +0 -716
- package/dist/implementation-CsQrexpY.d.ts +0 -716
- package/dist/types-ofe1fAYQ.d.mts +0 -38143
- package/dist/types-wuYJPoFb.d.ts +0 -38143
package/dist/agent/index.js
CHANGED
|
@@ -32,9 +32,11 @@ var agent_exports = {};
|
|
|
32
32
|
__export(agent_exports, {
|
|
33
33
|
AGENT_TRANSPORTS: () => AGENT_TRANSPORTS,
|
|
34
34
|
AgentParameterError: () => AgentParameterError,
|
|
35
|
+
AgentStateError: () => AgentStateError,
|
|
35
36
|
DEFAULT_TIMEOUT_MS: () => DEFAULT_TIMEOUT_MS,
|
|
36
37
|
MAX_TIMEOUT_MS: () => MAX_TIMEOUT_MS,
|
|
37
38
|
PROTOCOL_VERSION: () => PROTOCOL_VERSION,
|
|
39
|
+
STATE_MAX_BYTES: () => STATE_MAX_BYTES,
|
|
38
40
|
connectAgent: () => connectAgent,
|
|
39
41
|
normalizeReply: () => normalizeReply,
|
|
40
42
|
parameterSpecsFromSchema: () => parameterSpecsFromSchema,
|
|
@@ -83,14 +85,16 @@ var ConsoleLogger = class {
|
|
|
83
85
|
var import_api = require("@opentelemetry/api");
|
|
84
86
|
|
|
85
87
|
// src/agent/identity.ts
|
|
86
|
-
var os = __toESM(require("os"));
|
|
87
88
|
var import_node_crypto = require("crypto");
|
|
89
|
+
var os = __toESM(require("os"));
|
|
88
90
|
|
|
89
91
|
// package.json
|
|
90
|
-
var version = "
|
|
92
|
+
var version = "2.0.0";
|
|
91
93
|
|
|
92
94
|
// src/internal/constants.ts
|
|
93
95
|
var LANGWATCH_SDK_VERSION = version;
|
|
96
|
+
var API_VERSION_HEADER = "X-API-Version";
|
|
97
|
+
var LANGWATCH_API_VERSION = "2026-10-26";
|
|
94
98
|
var DEFAULT_ENDPOINT = "https://app.langwatch.ai";
|
|
95
99
|
|
|
96
100
|
// src/internal/endpoint.ts
|
|
@@ -145,7 +149,9 @@ function resolveEnabled({
|
|
|
145
149
|
env = process.env
|
|
146
150
|
}) {
|
|
147
151
|
const flag = env.LANGWATCH_AGENT_CONNECT;
|
|
148
|
-
if (isSet2(flag)
|
|
152
|
+
if (isSet2(flag)) {
|
|
153
|
+
if (!isTruthy(flag)) return false;
|
|
154
|
+
}
|
|
149
155
|
if (explicit !== void 0) return explicit;
|
|
150
156
|
return !isTruthy(env.CI);
|
|
151
157
|
}
|
|
@@ -228,24 +234,37 @@ var isStringList = (value) => Array.isArray(value) && value.every(isString);
|
|
|
228
234
|
var readScope = (value) => {
|
|
229
235
|
if (!isRecord(value)) return { kind: "shared" };
|
|
230
236
|
if (value.kind === "owner") return { kind: "owner" };
|
|
231
|
-
if (value.kind === "host" && isString(value.hostLabel))
|
|
237
|
+
if (value.kind === "host" && isString(value.hostLabel))
|
|
238
|
+
return { kind: "host", hostLabel: value.hostLabel };
|
|
232
239
|
return { kind: "shared" };
|
|
233
240
|
};
|
|
241
|
+
var readRegisteredAgent = (entry) => {
|
|
242
|
+
if (!isRecord(entry)) return null;
|
|
243
|
+
if (!isString(entry.name)) return null;
|
|
244
|
+
let id = null;
|
|
245
|
+
if (isString(entry.id)) {
|
|
246
|
+
id = entry.id;
|
|
247
|
+
} else if (isString(entry.agentId)) {
|
|
248
|
+
id = entry.agentId;
|
|
249
|
+
}
|
|
250
|
+
if (id === null) return null;
|
|
251
|
+
return {
|
|
252
|
+
name: entry.name,
|
|
253
|
+
environment: isString(entry.environment) ? entry.environment : "",
|
|
254
|
+
id,
|
|
255
|
+
url: isString(entry.url) ? entry.url : "",
|
|
256
|
+
parameterNotes: isStringList(entry.parameterNotes) ? entry.parameterNotes : [],
|
|
257
|
+
scope: readScope(entry.scope)
|
|
258
|
+
};
|
|
259
|
+
};
|
|
234
260
|
var readRegistered = (frame) => {
|
|
235
|
-
if (!Array.isArray(frame.agents)
|
|
261
|
+
if (!Array.isArray(frame.agents)) return null;
|
|
262
|
+
if (!isString(frame.instanceId)) return null;
|
|
236
263
|
const agents = [];
|
|
237
264
|
for (const entry of frame.agents) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
agents.push({
|
|
242
|
-
name: entry.name,
|
|
243
|
-
environment: isString(entry.environment) ? entry.environment : "",
|
|
244
|
-
id,
|
|
245
|
-
url: isString(entry.url) ? entry.url : "",
|
|
246
|
-
parameterNotes: isStringList(entry.parameterNotes) ? entry.parameterNotes : [],
|
|
247
|
-
scope: readScope(entry.scope)
|
|
248
|
-
});
|
|
265
|
+
const agent = readRegisteredAgent(entry);
|
|
266
|
+
if (agent === null) return null;
|
|
267
|
+
agents.push(agent);
|
|
249
268
|
}
|
|
250
269
|
return {
|
|
251
270
|
type: "registered",
|
|
@@ -281,12 +300,15 @@ var readDeadline = (value) => {
|
|
|
281
300
|
return null;
|
|
282
301
|
};
|
|
283
302
|
var readCall = (frame) => {
|
|
284
|
-
if (!isString(frame.callId)
|
|
303
|
+
if (!isString(frame.callId)) return null;
|
|
304
|
+
if (!isString(frame.agentId)) return null;
|
|
285
305
|
const messages = isMessageList(frame.messages) ? frame.messages : [];
|
|
286
306
|
const run = isRecord(frame.run) ? frame.run : {};
|
|
287
307
|
return {
|
|
288
308
|
type: "call",
|
|
289
309
|
protocol: typeof frame.protocol === "number" ? frame.protocol : PROTOCOL_VERSION,
|
|
310
|
+
// A platform that predates hooks sends no kind: every call is a turn.
|
|
311
|
+
kind: isString(frame.kind) ? frame.kind : "turn",
|
|
290
312
|
callId: frame.callId,
|
|
291
313
|
agentId: frame.agentId,
|
|
292
314
|
threadId: isString(frame.threadId) ? frame.threadId : "",
|
|
@@ -294,6 +316,7 @@ var readCall = (frame) => {
|
|
|
294
316
|
newMessages: isMessageList(frame.newMessages) ? frame.newMessages : messages,
|
|
295
317
|
params: readParams(frame.params),
|
|
296
318
|
session: frame.session === void 0 ? null : frame.session,
|
|
319
|
+
state: isRecord(frame.state) ? frame.state : null,
|
|
297
320
|
traceparent: isString(frame.traceparent) ? frame.traceparent : null,
|
|
298
321
|
deadlineAt: readDeadline(frame.deadlineAt),
|
|
299
322
|
run: {
|
|
@@ -310,7 +333,8 @@ function parseServerFrame(raw) {
|
|
|
310
333
|
} catch {
|
|
311
334
|
return null;
|
|
312
335
|
}
|
|
313
|
-
if (!isRecord(parsed)
|
|
336
|
+
if (!isRecord(parsed)) return null;
|
|
337
|
+
if (!isString(parsed.type)) return null;
|
|
314
338
|
switch (parsed.type) {
|
|
315
339
|
case "registered":
|
|
316
340
|
return readRegistered(parsed);
|
|
@@ -339,164 +363,6 @@ function traceIdFromTraceparent(traceparent) {
|
|
|
339
363
|
return traceId.toLowerCase();
|
|
340
364
|
}
|
|
341
365
|
|
|
342
|
-
// src/agent/schema.ts
|
|
343
|
-
var AgentParameterError = class extends Error {
|
|
344
|
-
constructor(message) {
|
|
345
|
-
super(message);
|
|
346
|
-
this.code = "agent_parameter_invalid";
|
|
347
|
-
this.name = "AgentParameterError";
|
|
348
|
-
}
|
|
349
|
-
};
|
|
350
|
-
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 })`;
|
|
351
|
-
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
352
|
-
var isStandardJsonSchema = (value) => {
|
|
353
|
-
if (!isRecord2(value)) return false;
|
|
354
|
-
const standard = value["~standard"];
|
|
355
|
-
return isRecord2(standard) && isRecord2(standard.jsonSchema);
|
|
356
|
-
};
|
|
357
|
-
var isJsonSchemaObject = (value) => isRecord2(value) && value.type === "object" && isRecord2(value.properties);
|
|
358
|
-
var isDefinitionMap = (value) => isRecord2(value) && Object.values(value).every(isRecord2);
|
|
359
|
-
var definitionType = (definition) => {
|
|
360
|
-
if (definition.type) return definition.type;
|
|
361
|
-
if (definition.options) return "string";
|
|
362
|
-
const value = definition.default;
|
|
363
|
-
if (typeof value === "number") return "number";
|
|
364
|
-
if (typeof value === "boolean") return "boolean";
|
|
365
|
-
return "string";
|
|
366
|
-
};
|
|
367
|
-
var definitionMapToSchema = (definitions) => {
|
|
368
|
-
const properties = {};
|
|
369
|
-
const required = [];
|
|
370
|
-
for (const [name, definition] of Object.entries(definitions)) {
|
|
371
|
-
const property = { type: definitionType(definition) };
|
|
372
|
-
if (definition.options) property.enum = [...definition.options];
|
|
373
|
-
if (definition.default !== void 0) property.default = definition.default;
|
|
374
|
-
else required.push(name);
|
|
375
|
-
if (definition.description !== void 0) property.description = definition.description;
|
|
376
|
-
properties[name] = property;
|
|
377
|
-
}
|
|
378
|
-
const schema = { type: "object", properties };
|
|
379
|
-
if (required.length > 0) schema.required = required;
|
|
380
|
-
return schema;
|
|
381
|
-
};
|
|
382
|
-
var readStandardJsonSchema = (input) => {
|
|
383
|
-
const converter = input["~standard"].jsonSchema;
|
|
384
|
-
const options = { target: "draft-2020-12" };
|
|
385
|
-
const schema = converter.input?.(options) ?? converter.output?.(options);
|
|
386
|
-
if (schema === void 0) {
|
|
387
|
-
throw new AgentParameterError(`the "~standard".jsonSchema converter has no input function; ${ACCEPTED_FORMS}`);
|
|
388
|
-
}
|
|
389
|
-
if (!isRecord2(schema)) {
|
|
390
|
-
throw new AgentParameterError(`the "~standard".jsonSchema converter returned no object; ${ACCEPTED_FORMS}`);
|
|
391
|
-
}
|
|
392
|
-
return schema;
|
|
393
|
-
};
|
|
394
|
-
function toParameterSchema(input) {
|
|
395
|
-
if (input === void 0) return { type: "object", properties: {} };
|
|
396
|
-
if (isStandardJsonSchema(input)) return readStandardJsonSchema(input);
|
|
397
|
-
if (isJsonSchemaObject(input)) return input;
|
|
398
|
-
if (isDefinitionMap(input)) return definitionMapToSchema(input);
|
|
399
|
-
throw new AgentParameterError(ACCEPTED_FORMS);
|
|
400
|
-
}
|
|
401
|
-
var specType = (property) => {
|
|
402
|
-
const type = Array.isArray(property.type) ? property.type.find((item) => item !== "null") : property.type;
|
|
403
|
-
if (type === "number" || type === "integer") return "number";
|
|
404
|
-
if (type === "boolean") return "boolean";
|
|
405
|
-
return "string";
|
|
406
|
-
};
|
|
407
|
-
var scalar = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : void 0;
|
|
408
|
-
function parameterSpecsFromSchema(schema) {
|
|
409
|
-
const properties = isRecord2(schema.properties) ? schema.properties : {};
|
|
410
|
-
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
411
|
-
const specs = [];
|
|
412
|
-
for (const [name, raw] of Object.entries(properties)) {
|
|
413
|
-
const property = isRecord2(raw) ? raw : {};
|
|
414
|
-
const spec = { name, type: specType(property) };
|
|
415
|
-
const options = Array.isArray(property.enum) ? property.enum.filter((item) => typeof item === "string") : void 0;
|
|
416
|
-
if (options && options.length > 0) spec.options = options;
|
|
417
|
-
const fallback = scalar(property.default);
|
|
418
|
-
if (fallback !== void 0) spec.default = fallback;
|
|
419
|
-
if (typeof property.description === "string") spec.description = property.description;
|
|
420
|
-
spec.required = fallback === void 0 && required.has(name);
|
|
421
|
-
specs.push(spec);
|
|
422
|
-
}
|
|
423
|
-
return specs;
|
|
424
|
-
}
|
|
425
|
-
var coerce = ({
|
|
426
|
-
spec,
|
|
427
|
-
value
|
|
428
|
-
}) => {
|
|
429
|
-
if (spec.type === "number") {
|
|
430
|
-
const asNumber = typeof value === "number" ? value : Number(value);
|
|
431
|
-
if (typeof value === "boolean" || !Number.isFinite(asNumber) || String(value).trim() === "") {
|
|
432
|
-
throw new AgentParameterError(`parameter "${spec.name}" must be a number, got ${JSON.stringify(value)}`);
|
|
433
|
-
}
|
|
434
|
-
return asNumber;
|
|
435
|
-
}
|
|
436
|
-
if (spec.type === "boolean") {
|
|
437
|
-
if (typeof value === "boolean") return value;
|
|
438
|
-
if (value === "true") return true;
|
|
439
|
-
if (value === "false") return false;
|
|
440
|
-
throw new AgentParameterError(`parameter "${spec.name}" must be true or false, got ${JSON.stringify(value)}`);
|
|
441
|
-
}
|
|
442
|
-
const asString = typeof value === "string" ? value : String(value);
|
|
443
|
-
if (spec.options && !spec.options.includes(asString)) {
|
|
444
|
-
throw new AgentParameterError(
|
|
445
|
-
`parameter "${spec.name}" must be one of ${spec.options.join(", ")}, got ${JSON.stringify(value)}`
|
|
446
|
-
);
|
|
447
|
-
}
|
|
448
|
-
return asString;
|
|
449
|
-
};
|
|
450
|
-
function resolveParameterValues({
|
|
451
|
-
specs,
|
|
452
|
-
supplied
|
|
453
|
-
}) {
|
|
454
|
-
const values = { ...supplied ?? {} };
|
|
455
|
-
for (const spec of specs) {
|
|
456
|
-
const value = values[spec.name];
|
|
457
|
-
if (value === void 0) {
|
|
458
|
-
if (spec.default !== void 0) {
|
|
459
|
-
values[spec.name] = spec.default;
|
|
460
|
-
continue;
|
|
461
|
-
}
|
|
462
|
-
if (!spec.required) continue;
|
|
463
|
-
throw new AgentParameterError(`parameter "${spec.name}" is required and the run did not supply it`);
|
|
464
|
-
}
|
|
465
|
-
values[spec.name] = coerce({ spec, value });
|
|
466
|
-
}
|
|
467
|
-
return values;
|
|
468
|
-
}
|
|
469
|
-
var issuePath = (issue) => (issue.path ?? []).map(
|
|
470
|
-
(segment) => typeof segment === "object" && segment !== null ? String(segment.key) : String(segment)
|
|
471
|
-
).join(".");
|
|
472
|
-
async function validateParameterValues({
|
|
473
|
-
schema,
|
|
474
|
-
values
|
|
475
|
-
}) {
|
|
476
|
-
const standard = schema["~standard"];
|
|
477
|
-
const result = await standard.validate?.(values);
|
|
478
|
-
if (result === void 0) return values;
|
|
479
|
-
if (result.issues) {
|
|
480
|
-
const detail = result.issues.map((issue) => {
|
|
481
|
-
const path = issuePath(issue);
|
|
482
|
-
return path ? `${path}: ${issue.message}` : issue.message;
|
|
483
|
-
}).join("; ");
|
|
484
|
-
throw new AgentParameterError(`parameters refused by the schema: ${detail}`);
|
|
485
|
-
}
|
|
486
|
-
const parsed = isRecord2(result.value) ? result.value : {};
|
|
487
|
-
return { ...values, ...parsed };
|
|
488
|
-
}
|
|
489
|
-
function createParameterReader({
|
|
490
|
-
input,
|
|
491
|
-
specs
|
|
492
|
-
}) {
|
|
493
|
-
const schema = isStandardJsonSchema(input) ? input : void 0;
|
|
494
|
-
return async (supplied) => {
|
|
495
|
-
const values = resolveParameterValues({ specs, supplied });
|
|
496
|
-
return schema ? validateParameterValues({ schema, values }) : values;
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
|
|
500
366
|
// src/agent/transport.ts
|
|
501
367
|
var import_node_module = require("module");
|
|
502
368
|
|
|
@@ -513,11 +379,7 @@ var CREDENTIAL_HEADERS = [
|
|
|
513
379
|
"x-project-id"
|
|
514
380
|
];
|
|
515
381
|
var LangWatchRedirectError = class extends Error {
|
|
516
|
-
constructor({
|
|
517
|
-
url,
|
|
518
|
-
location,
|
|
519
|
-
status
|
|
520
|
-
}) {
|
|
382
|
+
constructor({ url, location, status }) {
|
|
521
383
|
super(
|
|
522
384
|
`LangWatch refused to follow a redirect from ${url} to ${location ?? "an unknown location"} (HTTP ${status}). Set the endpoint to the final URL.`
|
|
523
385
|
);
|
|
@@ -527,6 +389,20 @@ var LangWatchRedirectError = class extends Error {
|
|
|
527
389
|
this.status = status;
|
|
528
390
|
}
|
|
529
391
|
};
|
|
392
|
+
var isPlainHeaders = (headers) => !(headers instanceof Headers) && !Array.isArray(headers);
|
|
393
|
+
var withApiVersion = ({
|
|
394
|
+
input,
|
|
395
|
+
init
|
|
396
|
+
}) => {
|
|
397
|
+
const sent = init?.headers ?? (typeof Request !== "undefined" && input instanceof Request ? input.headers : void 0);
|
|
398
|
+
if (new Headers(sent).has(API_VERSION_HEADER)) return { ...init };
|
|
399
|
+
if (sent === void 0 || isPlainHeaders(sent)) {
|
|
400
|
+
return { ...init, headers: { ...sent, [API_VERSION_HEADER]: LANGWATCH_API_VERSION } };
|
|
401
|
+
}
|
|
402
|
+
const headers = new Headers(sent);
|
|
403
|
+
headers.set(API_VERSION_HEADER, LANGWATCH_API_VERSION);
|
|
404
|
+
return { ...init, headers };
|
|
405
|
+
};
|
|
530
406
|
var warnedOrigins = /* @__PURE__ */ new Set();
|
|
531
407
|
var isRedirect = (response) => response.type === "opaqueredirect" || REDIRECT_STATUSES.has(response.status);
|
|
532
408
|
var isStream = (body) => typeof ReadableStream !== "undefined" && body instanceof ReadableStream;
|
|
@@ -664,7 +540,19 @@ var upgrade = async ({
|
|
|
664
540
|
const location = first.headers.get("location");
|
|
665
541
|
const refused = refusalOf({ url, response: first });
|
|
666
542
|
const target = location === null ? null : schemeUpgradeTarget({ url, location });
|
|
667
|
-
if (location === null
|
|
543
|
+
if (location === null) {
|
|
544
|
+
discard(spare);
|
|
545
|
+
throw refused;
|
|
546
|
+
}
|
|
547
|
+
if (first.status === 303) {
|
|
548
|
+
discard(spare);
|
|
549
|
+
throw refused;
|
|
550
|
+
}
|
|
551
|
+
if (target === null) {
|
|
552
|
+
discard(spare);
|
|
553
|
+
throw refused;
|
|
554
|
+
}
|
|
555
|
+
if (isStream(init?.body)) {
|
|
668
556
|
discard(spare);
|
|
669
557
|
throw refused;
|
|
670
558
|
}
|
|
@@ -687,7 +575,8 @@ var createLangWatchFetch = ({
|
|
|
687
575
|
} = {}) => {
|
|
688
576
|
const send = fetchImpl ?? ((input, init) => globalThis.fetch(input, init));
|
|
689
577
|
const log = logger ?? new ConsoleLogger({ level: "warn", prefix: "LangWatch" });
|
|
690
|
-
return async (input,
|
|
578
|
+
return async (input, given) => {
|
|
579
|
+
const init = withApiVersion({ input, init: given });
|
|
691
580
|
const url = requestUrl(input);
|
|
692
581
|
const effective = effectiveRequest({ input, init });
|
|
693
582
|
const method = (effective?.method ?? init?.method ?? "GET").toUpperCase();
|
|
@@ -714,7 +603,10 @@ function resolveTransport({
|
|
|
714
603
|
env = process.env
|
|
715
604
|
}) {
|
|
716
605
|
const candidate = isSet3(explicit) ? explicit : env.LANGWATCH_AGENT_TRANSPORT;
|
|
717
|
-
|
|
606
|
+
if (!isSet3(candidate)) return "websocket";
|
|
607
|
+
const normalized = candidate.trim().toLowerCase();
|
|
608
|
+
if (normalized === "http") return "http";
|
|
609
|
+
return "websocket";
|
|
718
610
|
}
|
|
719
611
|
var NoWebSocketError = class extends Error {
|
|
720
612
|
constructor() {
|
|
@@ -811,14 +703,14 @@ var HttpLongPollSocket = class {
|
|
|
811
703
|
return;
|
|
812
704
|
}
|
|
813
705
|
if (frame.type === "ack" && typeof frame.callId === "string") this.inFlight.add(frame.callId);
|
|
814
|
-
if (frame.type === "result" && typeof frame.callId === "string")
|
|
706
|
+
if (frame.type === "result" && typeof frame.callId === "string")
|
|
707
|
+
this.inFlight.delete(frame.callId);
|
|
815
708
|
this.outbox = this.outbox.then(() => this.registered).then(() => this.post(data)).catch(() => void 0);
|
|
816
709
|
}
|
|
817
710
|
/**
|
|
818
|
-
* Stops polling, lets
|
|
819
|
-
*
|
|
820
|
-
*
|
|
821
|
-
* hold the connection open.
|
|
711
|
+
* Stops polling, lets queued frames go out, then reports close. Bounded: on
|
|
712
|
+
* the deadline the frame requests are aborted and close is reported anyway,
|
|
713
|
+
* so an unanswering request cannot hold the connection open.
|
|
822
714
|
*/
|
|
823
715
|
close(code = 1e3) {
|
|
824
716
|
this.closed = true;
|
|
@@ -873,7 +765,8 @@ var HttpLongPollSocket = class {
|
|
|
873
765
|
signal: this.polls.signal
|
|
874
766
|
});
|
|
875
767
|
} catch (error) {
|
|
876
|
-
if (!this.closed)
|
|
768
|
+
if (!this.closed)
|
|
769
|
+
this.fail(`could not reach ${this.url}/register (${describe(error)})`, 1006);
|
|
877
770
|
return;
|
|
878
771
|
}
|
|
879
772
|
const body = await this.jsonOf(response);
|
|
@@ -894,46 +787,66 @@ var HttpLongPollSocket = class {
|
|
|
894
787
|
}
|
|
895
788
|
async pollLoop() {
|
|
896
789
|
while (!this.closed && this.token) {
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
if (
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
790
|
+
if (!await this.pollOnce()) return;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
/** One long poll: delivers what it answered, and says whether polling goes on. */
|
|
794
|
+
async pollOnce() {
|
|
795
|
+
const query = this.inFlight.size > 0 ? `?inFlight=${encodeURIComponent([...this.inFlight].join(","))}` : "";
|
|
796
|
+
const startedAt = Date.now();
|
|
797
|
+
let response;
|
|
798
|
+
try {
|
|
799
|
+
response = await this.fetchImpl(`${this.url}/poll${query}`, {
|
|
800
|
+
method: "GET",
|
|
801
|
+
headers: this.requestHeaders(),
|
|
802
|
+
signal: this.polls.signal
|
|
803
|
+
});
|
|
804
|
+
} catch (error) {
|
|
805
|
+
if (!this.closed) this.fail(`the poll failed (${describe(error)})`, 1006);
|
|
806
|
+
return false;
|
|
807
|
+
}
|
|
808
|
+
if (this.closed) return false;
|
|
809
|
+
if (response.status === 410) {
|
|
810
|
+
this.fail(
|
|
811
|
+
"the platform no longer knows this instance, registering again",
|
|
812
|
+
SESSION_LOST_CLOSE_CODE
|
|
813
|
+
);
|
|
814
|
+
return false;
|
|
815
|
+
}
|
|
816
|
+
const body = await this.jsonOf(response);
|
|
817
|
+
if (!response.ok) {
|
|
818
|
+
this.refusePoll({ status: response.status, body });
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
const delivered = this.deliverFrames(body);
|
|
822
|
+
for (const listener of this.pingListeners) listener();
|
|
823
|
+
if (delivered === 0) {
|
|
824
|
+
const elapsedMs = Date.now() - startedAt;
|
|
825
|
+
if (elapsedMs < EMPTY_POLL_FLOOR_MS) await wait(EMPTY_POLL_FLOOR_MS - elapsedMs);
|
|
826
|
+
}
|
|
827
|
+
return true;
|
|
828
|
+
}
|
|
829
|
+
refusePoll({
|
|
830
|
+
status,
|
|
831
|
+
body
|
|
832
|
+
}) {
|
|
833
|
+
const answered = body && typeof body.frame === "object" && body.frame !== null ? body.frame : null;
|
|
834
|
+
if (answered) this.emitMessage(JSON.stringify(answered));
|
|
835
|
+
if (answered?.type === "refused") {
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
this.fail(`the poll was answered with HTTP ${status}`, 1006);
|
|
839
|
+
}
|
|
840
|
+
/** Emits every frame a poll answered and returns how many there were. */
|
|
841
|
+
deliverFrames(body) {
|
|
842
|
+
const frames = Array.isArray(body?.frames) ? body.frames : [];
|
|
843
|
+
for (const frame of frames) {
|
|
844
|
+
const entry = frame;
|
|
845
|
+
if (entry.type === "cancel" && typeof entry.callId === "string")
|
|
846
|
+
this.inFlight.delete(entry.callId);
|
|
847
|
+
this.emitMessage(JSON.stringify(frame));
|
|
936
848
|
}
|
|
849
|
+
return frames.length;
|
|
937
850
|
}
|
|
938
851
|
async post(data) {
|
|
939
852
|
if (!this.token) return;
|
|
@@ -951,12 +864,7 @@ var HttpLongPollSocket = class {
|
|
|
951
864
|
if (this.frames.signal.aborted) return;
|
|
952
865
|
response = null;
|
|
953
866
|
}
|
|
954
|
-
if (response
|
|
955
|
-
if (response?.status === 410) {
|
|
956
|
-
this.fail("the platform no longer knows this instance, registering again", SESSION_LOST_CLOSE_CODE);
|
|
957
|
-
return;
|
|
958
|
-
}
|
|
959
|
-
if (response && response.status < 500) return;
|
|
867
|
+
if (this.postSettled(response)) return;
|
|
960
868
|
const delay = POST_RETRY_DELAYS_MS[attempt];
|
|
961
869
|
if (delay === void 0) {
|
|
962
870
|
this.fail(`a frame could not be posted after ${attempt} retries`, 1006);
|
|
@@ -965,6 +873,18 @@ var HttpLongPollSocket = class {
|
|
|
965
873
|
await wait(delay);
|
|
966
874
|
}
|
|
967
875
|
}
|
|
876
|
+
/** Whether a post's response ends its retries: delivered, refused, or the session lost. */
|
|
877
|
+
postSettled(response) {
|
|
878
|
+
if (response?.ok) return true;
|
|
879
|
+
if (response?.status === 410) {
|
|
880
|
+
this.fail(
|
|
881
|
+
"the platform no longer knows this instance, registering again",
|
|
882
|
+
SESSION_LOST_CLOSE_CODE
|
|
883
|
+
);
|
|
884
|
+
return true;
|
|
885
|
+
}
|
|
886
|
+
return response !== null && response.status < 500;
|
|
887
|
+
}
|
|
968
888
|
async jsonOf(response) {
|
|
969
889
|
try {
|
|
970
890
|
const parsed = await response.json();
|
|
@@ -1018,19 +938,257 @@ function openTransportSocket({
|
|
|
1018
938
|
return transport === "http" ? new HttpLongPollSocket({ url: httpUrl, headers }) : socketFactory({ url: websocketUrl, headers });
|
|
1019
939
|
}
|
|
1020
940
|
|
|
941
|
+
// src/agent/schema.ts
|
|
942
|
+
var AgentParameterError = class extends Error {
|
|
943
|
+
constructor(message) {
|
|
944
|
+
super(message);
|
|
945
|
+
this.code = "agent_parameter_invalid";
|
|
946
|
+
this.name = "AgentParameterError";
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
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 })`;
|
|
950
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
951
|
+
var isStandardJsonSchema = (value) => {
|
|
952
|
+
if (!isRecord2(value)) return false;
|
|
953
|
+
const standard = value["~standard"];
|
|
954
|
+
return isRecord2(standard) && isRecord2(standard.jsonSchema);
|
|
955
|
+
};
|
|
956
|
+
var isJsonSchemaObject = (value) => isRecord2(value) && value.type === "object" && isRecord2(value.properties);
|
|
957
|
+
var isDefinitionMap = (value) => isRecord2(value) && Object.values(value).every(isRecord2);
|
|
958
|
+
var definitionType = (definition) => {
|
|
959
|
+
if (definition.type) return definition.type;
|
|
960
|
+
if (definition.options) return "string";
|
|
961
|
+
const value = definition.default;
|
|
962
|
+
if (typeof value === "number") return "number";
|
|
963
|
+
if (typeof value === "boolean") return "boolean";
|
|
964
|
+
return "string";
|
|
965
|
+
};
|
|
966
|
+
var definitionMapToSchema = (definitions) => {
|
|
967
|
+
const properties = {};
|
|
968
|
+
const required = [];
|
|
969
|
+
for (const [name, definition] of Object.entries(definitions)) {
|
|
970
|
+
const property = { type: definitionType(definition) };
|
|
971
|
+
if (definition.options) property.enum = [...definition.options];
|
|
972
|
+
if (definition.default !== void 0) property.default = definition.default;
|
|
973
|
+
else required.push(name);
|
|
974
|
+
if (definition.description !== void 0) property.description = definition.description;
|
|
975
|
+
properties[name] = property;
|
|
976
|
+
}
|
|
977
|
+
const schema = { type: "object", properties };
|
|
978
|
+
if (required.length > 0) schema.required = required;
|
|
979
|
+
return schema;
|
|
980
|
+
};
|
|
981
|
+
var readStandardJsonSchema = (input) => {
|
|
982
|
+
const converter = input["~standard"].jsonSchema;
|
|
983
|
+
const options = { target: "draft-2020-12" };
|
|
984
|
+
const schema = converter.input?.(options) ?? converter.output?.(options);
|
|
985
|
+
if (schema === void 0) {
|
|
986
|
+
throw new AgentParameterError(
|
|
987
|
+
`the "~standard".jsonSchema converter has no input function; ${ACCEPTED_FORMS}`
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
if (!isRecord2(schema)) {
|
|
991
|
+
throw new AgentParameterError(
|
|
992
|
+
`the "~standard".jsonSchema converter returned no object; ${ACCEPTED_FORMS}`
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
return schema;
|
|
996
|
+
};
|
|
997
|
+
function toParameterSchema(input) {
|
|
998
|
+
if (input === void 0) return { type: "object", properties: {} };
|
|
999
|
+
if (isStandardJsonSchema(input)) return readStandardJsonSchema(input);
|
|
1000
|
+
if (isJsonSchemaObject(input)) return input;
|
|
1001
|
+
if (isDefinitionMap(input)) return definitionMapToSchema(input);
|
|
1002
|
+
throw new AgentParameterError(ACCEPTED_FORMS);
|
|
1003
|
+
}
|
|
1004
|
+
var specType = (property) => {
|
|
1005
|
+
const type = Array.isArray(property.type) ? property.type.find((item) => item !== "null") : property.type;
|
|
1006
|
+
if (type === "number" || type === "integer") return "number";
|
|
1007
|
+
if (type === "boolean") return "boolean";
|
|
1008
|
+
return "string";
|
|
1009
|
+
};
|
|
1010
|
+
var scalar = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : void 0;
|
|
1011
|
+
function parameterSpecsFromSchema(schema) {
|
|
1012
|
+
const properties = isRecord2(schema.properties) ? schema.properties : {};
|
|
1013
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
1014
|
+
const specs = [];
|
|
1015
|
+
for (const [name, raw] of Object.entries(properties)) {
|
|
1016
|
+
const property = isRecord2(raw) ? raw : {};
|
|
1017
|
+
const spec = { name, type: specType(property) };
|
|
1018
|
+
const options = Array.isArray(property.enum) ? property.enum.filter((item) => typeof item === "string") : void 0;
|
|
1019
|
+
if (options && options.length > 0) spec.options = options;
|
|
1020
|
+
const fallback = scalar(property.default);
|
|
1021
|
+
if (fallback !== void 0) spec.default = fallback;
|
|
1022
|
+
if (typeof property.description === "string") spec.description = property.description;
|
|
1023
|
+
spec.required = fallback === void 0 && required.has(name);
|
|
1024
|
+
specs.push(spec);
|
|
1025
|
+
}
|
|
1026
|
+
return specs;
|
|
1027
|
+
}
|
|
1028
|
+
var coerce = ({
|
|
1029
|
+
spec,
|
|
1030
|
+
value
|
|
1031
|
+
}) => {
|
|
1032
|
+
if (spec.type === "number") {
|
|
1033
|
+
return coerceNumber(spec.name, value);
|
|
1034
|
+
}
|
|
1035
|
+
if (spec.type === "boolean") {
|
|
1036
|
+
if (typeof value === "boolean") return value;
|
|
1037
|
+
if (value === "true") return true;
|
|
1038
|
+
if (value === "false") return false;
|
|
1039
|
+
throw new AgentParameterError(
|
|
1040
|
+
`parameter "${spec.name}" must be true or false, got ${JSON.stringify(value)}`
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
const asString = typeof value === "string" ? value : String(value);
|
|
1044
|
+
if (spec.options && !spec.options.includes(asString)) {
|
|
1045
|
+
throw new AgentParameterError(
|
|
1046
|
+
`parameter "${spec.name}" must be one of ${spec.options.join(", ")}, got ${JSON.stringify(value)}`
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
return asString;
|
|
1050
|
+
};
|
|
1051
|
+
function resolveParameterValues({
|
|
1052
|
+
specs,
|
|
1053
|
+
supplied
|
|
1054
|
+
}) {
|
|
1055
|
+
const values = { ...supplied };
|
|
1056
|
+
for (const spec of specs) {
|
|
1057
|
+
const value = values[spec.name];
|
|
1058
|
+
if (value === void 0) {
|
|
1059
|
+
if (spec.default !== void 0) {
|
|
1060
|
+
values[spec.name] = spec.default;
|
|
1061
|
+
continue;
|
|
1062
|
+
}
|
|
1063
|
+
if (!spec.required) continue;
|
|
1064
|
+
throw new AgentParameterError(
|
|
1065
|
+
`parameter "${spec.name}" is required and the run did not supply it`
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
values[spec.name] = coerce({ spec, value });
|
|
1069
|
+
}
|
|
1070
|
+
return values;
|
|
1071
|
+
}
|
|
1072
|
+
var issuePath = (issue) => (issue.path ?? []).map(
|
|
1073
|
+
(segment) => typeof segment === "object" && segment !== null ? String(segment.key) : String(segment)
|
|
1074
|
+
).join(".");
|
|
1075
|
+
async function validateParameterValues({
|
|
1076
|
+
schema,
|
|
1077
|
+
values
|
|
1078
|
+
}) {
|
|
1079
|
+
const standard = schema["~standard"];
|
|
1080
|
+
const result = await standard.validate?.(values);
|
|
1081
|
+
if (result === void 0) return values;
|
|
1082
|
+
if (result.issues) {
|
|
1083
|
+
const detail = result.issues.map((issue) => {
|
|
1084
|
+
const path = issuePath(issue);
|
|
1085
|
+
return path ? `${path}: ${issue.message}` : issue.message;
|
|
1086
|
+
}).join("; ");
|
|
1087
|
+
throw new AgentParameterError(`parameters refused by the schema: ${detail}`);
|
|
1088
|
+
}
|
|
1089
|
+
const parsed = isRecord2(result.value) ? result.value : {};
|
|
1090
|
+
return { ...values, ...parsed };
|
|
1091
|
+
}
|
|
1092
|
+
function createParameterReader({
|
|
1093
|
+
input,
|
|
1094
|
+
specs
|
|
1095
|
+
}) {
|
|
1096
|
+
const schema = isStandardJsonSchema(input) ? input : void 0;
|
|
1097
|
+
return async (supplied) => {
|
|
1098
|
+
const values = resolveParameterValues({ specs, supplied });
|
|
1099
|
+
return schema ? validateParameterValues({ schema, values }) : values;
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
function coerceNumber(name, value) {
|
|
1103
|
+
const asNumber = typeof value === "number" ? value : Number(value);
|
|
1104
|
+
if (typeof value === "boolean") {
|
|
1105
|
+
throw new AgentParameterError(
|
|
1106
|
+
`parameter "${name}" must be a number, got ${JSON.stringify(value)}`
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1109
|
+
if (!Number.isFinite(asNumber)) {
|
|
1110
|
+
throw new AgentParameterError(
|
|
1111
|
+
`parameter "${name}" must be a number, got ${JSON.stringify(value)}`
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
const stringValue2 = String(value).trim();
|
|
1115
|
+
if (stringValue2 === "") {
|
|
1116
|
+
throw new AgentParameterError(
|
|
1117
|
+
`parameter "${name}" must be a number, got ${JSON.stringify(value)}`
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
return asNumber;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// src/agent/state.ts
|
|
1124
|
+
var STATE_MAX_BYTES = 64 * 1024;
|
|
1125
|
+
var AgentStateError = class extends Error {
|
|
1126
|
+
constructor(code, message) {
|
|
1127
|
+
super(message);
|
|
1128
|
+
this.code = code;
|
|
1129
|
+
this.name = "AgentStateError";
|
|
1130
|
+
}
|
|
1131
|
+
};
|
|
1132
|
+
var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1133
|
+
function checkState(value) {
|
|
1134
|
+
if (value === void 0 || value === null) return void 0;
|
|
1135
|
+
if (!isPlainObject(value)) {
|
|
1136
|
+
throw new AgentStateError(
|
|
1137
|
+
"agent_call_failed",
|
|
1138
|
+
`the state must be a JSON object, got ${Array.isArray(value) ? "an array" : typeof value}`
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
let encoded;
|
|
1142
|
+
try {
|
|
1143
|
+
encoded = JSON.stringify(value);
|
|
1144
|
+
} catch (error) {
|
|
1145
|
+
throw new AgentStateError(
|
|
1146
|
+
"agent_call_failed",
|
|
1147
|
+
`the state is not JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
const sizeBytes = Buffer.byteLength(encoded, "utf8");
|
|
1151
|
+
if (sizeBytes > STATE_MAX_BYTES) {
|
|
1152
|
+
throw new AgentStateError(
|
|
1153
|
+
"agent_payload_too_large",
|
|
1154
|
+
`The state is ${sizeBytes} bytes, above the limit of ${STATE_MAX_BYTES} bytes.`
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
return value;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1021
1160
|
// src/agent/client.ts
|
|
1161
|
+
var kindRefusal = (runtime, kind) => {
|
|
1162
|
+
if (kind === "turn") return null;
|
|
1163
|
+
if (kind !== "setup" && kind !== "teardown") {
|
|
1164
|
+
return { code: "agent_call_failed", message: `unknown call kind "${kind}"` };
|
|
1165
|
+
}
|
|
1166
|
+
if (runtime.hooks[kind]) return null;
|
|
1167
|
+
return {
|
|
1168
|
+
code: "hook_not_declared",
|
|
1169
|
+
message: `agent "${runtime.name}" declares no ${kind} hook`
|
|
1170
|
+
};
|
|
1171
|
+
};
|
|
1172
|
+
var errorCodeOf = (error) => error instanceof AgentParameterError || error instanceof AgentStateError ? error.code : "agent_call_failed";
|
|
1173
|
+
var answerCall = async ({
|
|
1174
|
+
runtime,
|
|
1175
|
+
frame,
|
|
1176
|
+
call
|
|
1177
|
+
}) => {
|
|
1178
|
+
const hook = frame.kind === "setup" || frame.kind === "teardown" ? runtime.hooks[frame.kind] : void 0;
|
|
1179
|
+
if (!hook) {
|
|
1180
|
+
const result = await runtime.run(call);
|
|
1181
|
+
return { ...result, state: checkState(result.state) };
|
|
1182
|
+
}
|
|
1183
|
+
const state = await hook({ run: frame.run, state: frame.state, params: call.params });
|
|
1184
|
+
return { state: checkState(frame.kind === "setup" ? state ?? {} : state) };
|
|
1185
|
+
};
|
|
1022
1186
|
var FAILURE_NOTICE_INTERVAL_MS = 5 * 6e4;
|
|
1023
1187
|
var NOT_CONNECTED = "not connected to LangWatch";
|
|
1024
1188
|
function refusalAdvice(frame) {
|
|
1025
1189
|
switch (frame.code) {
|
|
1026
1190
|
case "project_required": {
|
|
1027
|
-
const
|
|
1028
|
-
const listed = projects.map((project) => {
|
|
1029
|
-
const entry = project;
|
|
1030
|
-
const id = typeof entry.id === "string" ? entry.id : "";
|
|
1031
|
-
const name = typeof entry.name === "string" ? entry.name : "";
|
|
1032
|
-
return name && id ? `${name} (${id})` : id || name;
|
|
1033
|
-
}).filter((line) => line !== "");
|
|
1191
|
+
const listed = projectNames(frame.meta?.projects);
|
|
1034
1192
|
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"}.`;
|
|
1035
1193
|
}
|
|
1036
1194
|
case "api_key_invalid":
|
|
@@ -1050,8 +1208,30 @@ function refusalAdvice(frame) {
|
|
|
1050
1208
|
return `${frame.message} (${frame.code})`;
|
|
1051
1209
|
}
|
|
1052
1210
|
}
|
|
1211
|
+
function projectNames(value) {
|
|
1212
|
+
if (!Array.isArray(value)) return [];
|
|
1213
|
+
return value.map(projectName).filter((name) => name !== "");
|
|
1214
|
+
}
|
|
1215
|
+
function projectName(value) {
|
|
1216
|
+
if (!isRecord3(value)) return "";
|
|
1217
|
+
const id = stringValue(value.id);
|
|
1218
|
+
const name = stringValue(value.name);
|
|
1219
|
+
if (name && id) return `${name} (${id})`;
|
|
1220
|
+
return id || name;
|
|
1221
|
+
}
|
|
1222
|
+
function isRecord3(value) {
|
|
1223
|
+
return typeof value === "object" && value !== null;
|
|
1224
|
+
}
|
|
1225
|
+
function stringValue(value) {
|
|
1226
|
+
return typeof value === "string" ? value : "";
|
|
1227
|
+
}
|
|
1053
1228
|
var SHUTDOWN_SIGNALS = ["SIGINT", "SIGTERM"];
|
|
1054
1229
|
var CLOSE_GRACE_MS = 500;
|
|
1230
|
+
var readCallTraceContext = (frame) => {
|
|
1231
|
+
const parent = frame.traceparent ? import_api.propagation.extract(import_api.context.active(), { traceparent: frame.traceparent }) : import_api.context.active();
|
|
1232
|
+
const traceId = import_api.trace.getSpanContext(parent)?.traceId ?? traceIdFromTraceparent(frame.traceparent) ?? "";
|
|
1233
|
+
return { parent, traceId };
|
|
1234
|
+
};
|
|
1055
1235
|
var AgentClient = class {
|
|
1056
1236
|
constructor(config) {
|
|
1057
1237
|
this.agents = [];
|
|
@@ -1092,7 +1272,7 @@ var AgentClient = class {
|
|
|
1092
1272
|
get isRegistered() {
|
|
1093
1273
|
return this.registered;
|
|
1094
1274
|
}
|
|
1095
|
-
/** True once the client gave up: refused, or no socket implementation
|
|
1275
|
+
/** True once the client gave up: refused, or no socket implementation available. */
|
|
1096
1276
|
get isStopped() {
|
|
1097
1277
|
return this.stopped;
|
|
1098
1278
|
}
|
|
@@ -1109,7 +1289,9 @@ var AgentClient = class {
|
|
|
1109
1289
|
addAgent(runtime) {
|
|
1110
1290
|
this.agents.push(runtime);
|
|
1111
1291
|
if (this.gaveUp) {
|
|
1112
|
-
this.logger.debug(
|
|
1292
|
+
this.logger.debug(
|
|
1293
|
+
`agent "${runtime.name}" ${NOT_CONNECTED}: the connection gave up earlier in this process`
|
|
1294
|
+
);
|
|
1113
1295
|
return;
|
|
1114
1296
|
}
|
|
1115
1297
|
this.stopped = false;
|
|
@@ -1119,6 +1301,10 @@ var AgentClient = class {
|
|
|
1119
1301
|
}
|
|
1120
1302
|
this.scheduleConnect(0);
|
|
1121
1303
|
}
|
|
1304
|
+
/** Registers the agents again after one changed: a hook declared once the socket opened. */
|
|
1305
|
+
refreshAgents() {
|
|
1306
|
+
if (this.socket) this.restartSocket();
|
|
1307
|
+
}
|
|
1122
1308
|
/** Closes the socket and connects again at once, keeping the reconnect loop. */
|
|
1123
1309
|
restartSocket() {
|
|
1124
1310
|
const socket = this.socket;
|
|
@@ -1278,7 +1464,10 @@ var AgentClient = class {
|
|
|
1278
1464
|
* connection is lost, and silence while the retries run: the same notice
|
|
1279
1465
|
* repeats only after the notice interval, and a reconnect resets it.
|
|
1280
1466
|
*/
|
|
1281
|
-
noteDisconnected({
|
|
1467
|
+
noteDisconnected({
|
|
1468
|
+
wasRegistered,
|
|
1469
|
+
code
|
|
1470
|
+
}) {
|
|
1282
1471
|
const now = Date.now();
|
|
1283
1472
|
if (wasRegistered) {
|
|
1284
1473
|
this.logger.warn(
|
|
@@ -1371,7 +1560,8 @@ var AgentClient = class {
|
|
|
1371
1560
|
this.logger.info(`connected to LangWatch over HTTP long polling at ${this.httpUrl}`);
|
|
1372
1561
|
}
|
|
1373
1562
|
this.heartbeatIntervalMs = frame.heartbeatIntervalMs;
|
|
1374
|
-
if (frame.instanceId && frame.instanceId !== this.instance.id)
|
|
1563
|
+
if (frame.instanceId && frame.instanceId !== this.instance.id)
|
|
1564
|
+
this.instance.id = frame.instanceId;
|
|
1375
1565
|
this.byId.clear();
|
|
1376
1566
|
for (const entry of frame.agents) {
|
|
1377
1567
|
const runtime = this.agents.find(
|
|
@@ -1398,21 +1588,9 @@ var AgentClient = class {
|
|
|
1398
1588
|
});
|
|
1399
1589
|
return;
|
|
1400
1590
|
}
|
|
1401
|
-
const
|
|
1402
|
-
if (
|
|
1403
|
-
this.sendError({
|
|
1404
|
-
callId: frame.callId,
|
|
1405
|
-
code: "agent_busy",
|
|
1406
|
-
message: `agent "${runtime.name}" has ${busy} call${busy === 1 ? "" : "s"} in flight, its limit`
|
|
1407
|
-
});
|
|
1408
|
-
return;
|
|
1409
|
-
}
|
|
1410
|
-
if (frame.deadlineAt !== null && frame.deadlineAt <= Date.now()) {
|
|
1411
|
-
this.sendError({
|
|
1412
|
-
callId: frame.callId,
|
|
1413
|
-
code: "agent_call_timeout",
|
|
1414
|
-
message: "the call deadline passed before it started"
|
|
1415
|
-
});
|
|
1591
|
+
const refusal = this.callRefusal({ frame, runtime });
|
|
1592
|
+
if (refusal) {
|
|
1593
|
+
this.sendError({ callId: frame.callId, ...refusal });
|
|
1416
1594
|
return;
|
|
1417
1595
|
}
|
|
1418
1596
|
const entry = { runtime, cancelled: false, timer: null };
|
|
@@ -1435,18 +1613,18 @@ var AgentClient = class {
|
|
|
1435
1613
|
}
|
|
1436
1614
|
this.send({ type: "ack", protocol: PROTOCOL_VERSION, callId: frame.callId });
|
|
1437
1615
|
this.armCallDeadline({ frame, entry, runtime });
|
|
1438
|
-
const parent
|
|
1439
|
-
const traceId = import_api.trace.getSpanContext(parent)?.traceId ?? traceIdFromTraceparent(frame.traceparent) ?? "";
|
|
1616
|
+
const { parent, traceId } = readCallTraceContext(frame);
|
|
1440
1617
|
const call = {
|
|
1441
1618
|
messages: frame.messages,
|
|
1442
1619
|
newMessages: frame.newMessages,
|
|
1443
1620
|
threadId: frame.threadId,
|
|
1444
1621
|
session: frame.session,
|
|
1622
|
+
state: frame.state,
|
|
1445
1623
|
params,
|
|
1446
1624
|
traceId
|
|
1447
1625
|
};
|
|
1448
1626
|
try {
|
|
1449
|
-
const result = await import_api.context.with(parent, () => runtime
|
|
1627
|
+
const result = await import_api.context.with(parent, () => answerCall({ runtime, frame, call }));
|
|
1450
1628
|
if (entry.cancelled) return;
|
|
1451
1629
|
this.releaseCall({ callId: frame.callId, entry });
|
|
1452
1630
|
await this.flushSpans();
|
|
@@ -1454,12 +1632,13 @@ var AgentClient = class {
|
|
|
1454
1632
|
type: "result",
|
|
1455
1633
|
protocol: PROTOCOL_VERSION,
|
|
1456
1634
|
callId: frame.callId,
|
|
1457
|
-
output: result.output,
|
|
1458
|
-
...result.session === void 0 ? {} : { session: result.session }
|
|
1635
|
+
...result.output === void 0 ? {} : { output: result.output },
|
|
1636
|
+
...result.session === void 0 ? {} : { session: result.session },
|
|
1637
|
+
...result.state === void 0 ? {} : { state: result.state }
|
|
1459
1638
|
});
|
|
1460
1639
|
} catch (error) {
|
|
1461
1640
|
if (entry.cancelled) return;
|
|
1462
|
-
const code = error
|
|
1641
|
+
const code = errorCodeOf(error);
|
|
1463
1642
|
const message = describeError(error);
|
|
1464
1643
|
this.logger.warn(`agent "${runtime.name}" call ${frame.callId} failed: ${message}`);
|
|
1465
1644
|
this.releaseCall({ callId: frame.callId, entry });
|
|
@@ -1469,15 +1648,32 @@ var AgentClient = class {
|
|
|
1469
1648
|
this.releaseCall({ callId: frame.callId, entry });
|
|
1470
1649
|
}
|
|
1471
1650
|
}
|
|
1651
|
+
/** Why the runtime cannot take this call now, or null when it can. */
|
|
1652
|
+
callRefusal({
|
|
1653
|
+
frame,
|
|
1654
|
+
runtime
|
|
1655
|
+
}) {
|
|
1656
|
+
const kind = kindRefusal(runtime, frame.kind);
|
|
1657
|
+
if (kind) return kind;
|
|
1658
|
+
const busy = [...this.inFlight.values()].filter((call) => call.runtime === runtime).length;
|
|
1659
|
+
if (busy >= runtime.concurrency) {
|
|
1660
|
+
return {
|
|
1661
|
+
code: "agent_busy",
|
|
1662
|
+
message: `agent "${runtime.name}" has ${busy} call${busy === 1 ? "" : "s"} in flight, its limit`
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
if (frame.deadlineAt !== null && frame.deadlineAt <= Date.now()) {
|
|
1666
|
+
return {
|
|
1667
|
+
code: "agent_call_timeout",
|
|
1668
|
+
message: "the call deadline passed before it started"
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
return null;
|
|
1672
|
+
}
|
|
1472
1673
|
/**
|
|
1473
|
-
* Exports the spans of the call now
|
|
1474
|
-
*
|
|
1475
|
-
*
|
|
1476
|
-
* made the judge report the spans missing.
|
|
1477
|
-
*
|
|
1478
|
-
* The call awaits this before it sends its result or its error: the frame is
|
|
1479
|
-
* what tells the platform the turn is over, so a frame that goes out first
|
|
1480
|
-
* lets the judge read the call while its spans are still in the exporter.
|
|
1674
|
+
* Exports the spans of the call now, since a batch exporter would
|
|
1675
|
+
* otherwise hold them for seconds after the judge reads them. Awaited
|
|
1676
|
+
* before the result or error frame goes out.
|
|
1481
1677
|
*/
|
|
1482
1678
|
async flushSpans() {
|
|
1483
1679
|
const provider = import_api.trace.getTracerProvider();
|
|
@@ -1509,26 +1705,33 @@ var AgentClient = class {
|
|
|
1509
1705
|
runtime
|
|
1510
1706
|
}) {
|
|
1511
1707
|
const fromDeadline = frame.deadlineAt === null ? Infinity : frame.deadlineAt - Date.now();
|
|
1512
|
-
const limit = Math.min(fromDeadline, runtime.timeoutMs);
|
|
1708
|
+
const limit = frame.kind !== "turn" && frame.deadlineAt !== null ? fromDeadline : Math.min(fromDeadline, runtime.timeoutMs);
|
|
1513
1709
|
if (!Number.isFinite(limit)) return;
|
|
1514
|
-
const timer = setTimeout(
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1710
|
+
const timer = setTimeout(
|
|
1711
|
+
() => {
|
|
1712
|
+
entry.timer = null;
|
|
1713
|
+
if (entry.cancelled) return;
|
|
1714
|
+
entry.cancelled = true;
|
|
1715
|
+
this.releaseCall({ callId: frame.callId, entry });
|
|
1716
|
+
this.logger.warn(
|
|
1717
|
+
`agent "${runtime.name}" call ${frame.callId} passed its ${limit} ms limit`
|
|
1718
|
+
);
|
|
1719
|
+
this.sendError({
|
|
1720
|
+
callId: frame.callId,
|
|
1721
|
+
code: "agent_call_timeout",
|
|
1722
|
+
message: `the call passed the ${limit} ms limit of agent "${runtime.name}"`
|
|
1723
|
+
});
|
|
1724
|
+
},
|
|
1725
|
+
Math.max(0, limit)
|
|
1726
|
+
);
|
|
1528
1727
|
timer.unref();
|
|
1529
1728
|
entry.timer = timer;
|
|
1530
1729
|
}
|
|
1531
|
-
sendError({
|
|
1730
|
+
sendError({
|
|
1731
|
+
callId,
|
|
1732
|
+
code,
|
|
1733
|
+
message
|
|
1734
|
+
}) {
|
|
1532
1735
|
this.send({ type: "result", protocol: PROTOCOL_VERSION, callId, error: { code, message } });
|
|
1533
1736
|
}
|
|
1534
1737
|
guard(action) {
|
|
@@ -1571,7 +1774,11 @@ var installShutdownHooks = () => {
|
|
|
1571
1774
|
}
|
|
1572
1775
|
process.on("beforeExit", onBeforeExit);
|
|
1573
1776
|
};
|
|
1574
|
-
function warnOnce2({
|
|
1777
|
+
function warnOnce2({
|
|
1778
|
+
logger,
|
|
1779
|
+
key,
|
|
1780
|
+
message
|
|
1781
|
+
}) {
|
|
1575
1782
|
if (noticesGiven.has(key)) {
|
|
1576
1783
|
logger.debug(message);
|
|
1577
1784
|
return;
|
|
@@ -1614,22 +1821,33 @@ var DEFAULT_CONCURRENCY = 10;
|
|
|
1614
1821
|
var isMessage = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.role === "string";
|
|
1615
1822
|
function normalizeReply(reply) {
|
|
1616
1823
|
if (typeof reply === "string") return { output: reply };
|
|
1617
|
-
if (Array.isArray(reply)
|
|
1824
|
+
if (Array.isArray(reply)) {
|
|
1825
|
+
if (reply.every(isMessage)) return { output: reply };
|
|
1826
|
+
}
|
|
1618
1827
|
if (isMessage(reply)) return { output: reply };
|
|
1619
1828
|
if (typeof reply === "object" && reply !== null && "output" in reply) {
|
|
1620
|
-
const { output, session } = reply;
|
|
1621
|
-
|
|
1622
|
-
|
|
1829
|
+
const { output, session, state } = reply;
|
|
1830
|
+
return {
|
|
1831
|
+
output: normalizeReply(output).output,
|
|
1832
|
+
...session === void 0 ? {} : { session },
|
|
1833
|
+
...state === void 0 ? {} : { state }
|
|
1834
|
+
};
|
|
1623
1835
|
}
|
|
1624
1836
|
throw new Error(
|
|
1625
|
-
"the agent handler must return a string, a message, a list of messages, or { output, session }"
|
|
1837
|
+
"the agent handler must return a string, a message, a list of messages, or { output, session, state }"
|
|
1626
1838
|
);
|
|
1627
1839
|
}
|
|
1628
|
-
var clampTimeout = ({
|
|
1840
|
+
var clampTimeout = ({
|
|
1841
|
+
timeoutMs,
|
|
1842
|
+
logger,
|
|
1843
|
+
name
|
|
1844
|
+
}) => {
|
|
1629
1845
|
if (timeoutMs === void 0) return DEFAULT_TIMEOUT_MS;
|
|
1630
1846
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return DEFAULT_TIMEOUT_MS;
|
|
1631
1847
|
if (timeoutMs > MAX_TIMEOUT_MS) {
|
|
1632
|
-
logger.warn(
|
|
1848
|
+
logger.warn(
|
|
1849
|
+
`agent "${name}": timeoutMs ${timeoutMs} is above the ${MAX_TIMEOUT_MS} cap, using the cap`
|
|
1850
|
+
);
|
|
1633
1851
|
return MAX_TIMEOUT_MS;
|
|
1634
1852
|
}
|
|
1635
1853
|
return Math.floor(timeoutMs);
|
|
@@ -1660,10 +1878,12 @@ function connectAgent(options, handler) {
|
|
|
1660
1878
|
newMessages: call.newMessages ?? call.messages,
|
|
1661
1879
|
threadId: call.threadId ?? `local_${Date.now().toString(36)}`,
|
|
1662
1880
|
session: call.session ?? null,
|
|
1881
|
+
state: call.state ?? null,
|
|
1663
1882
|
params,
|
|
1664
1883
|
traceId: call.traceId ?? ""
|
|
1665
1884
|
});
|
|
1666
1885
|
};
|
|
1886
|
+
const hooks = {};
|
|
1667
1887
|
const runtime = {
|
|
1668
1888
|
name,
|
|
1669
1889
|
environment,
|
|
@@ -1673,14 +1893,29 @@ function connectAgent(options, handler) {
|
|
|
1673
1893
|
parameters,
|
|
1674
1894
|
concurrency,
|
|
1675
1895
|
timeoutMs,
|
|
1676
|
-
...options.sticky ? { sticky: true } : {}
|
|
1896
|
+
...options.sticky ? { sticky: true } : {},
|
|
1897
|
+
hooks: { setup: false, teardown: false }
|
|
1677
1898
|
},
|
|
1678
1899
|
readParams: readParams2,
|
|
1679
1900
|
concurrency,
|
|
1680
1901
|
timeoutMs,
|
|
1681
|
-
run: runHandler
|
|
1902
|
+
run: runHandler,
|
|
1903
|
+
hooks
|
|
1682
1904
|
};
|
|
1683
1905
|
let detach;
|
|
1906
|
+
let refresh;
|
|
1907
|
+
const scenario = {
|
|
1908
|
+
setup: (hook) => {
|
|
1909
|
+
hooks.setup = async ({ run, params }) => hook(run, params);
|
|
1910
|
+
runtime.register.hooks = { setup: true, teardown: Boolean(hooks.teardown) };
|
|
1911
|
+
refresh?.();
|
|
1912
|
+
},
|
|
1913
|
+
teardown: (hook) => {
|
|
1914
|
+
hooks.teardown = async ({ run, state, params }) => hook(run, state, params);
|
|
1915
|
+
runtime.register.hooks = { setup: Boolean(hooks.setup), teardown: true };
|
|
1916
|
+
refresh?.();
|
|
1917
|
+
}
|
|
1918
|
+
};
|
|
1684
1919
|
if (!resolveEnabled({ explicit: options.enabled })) {
|
|
1685
1920
|
logger.debug(`agent "${name}" not connected to LangWatch: the connection is disabled`);
|
|
1686
1921
|
} else {
|
|
@@ -1702,15 +1937,18 @@ function connectAgent(options, handler) {
|
|
|
1702
1937
|
});
|
|
1703
1938
|
client.addAgent(runtime);
|
|
1704
1939
|
detach = () => client.removeAgent(runtime);
|
|
1940
|
+
refresh = () => client.refreshAgents();
|
|
1705
1941
|
}
|
|
1706
1942
|
}
|
|
1707
1943
|
const connected = Object.assign(invoke, {
|
|
1708
1944
|
environment,
|
|
1709
1945
|
parameters,
|
|
1946
|
+
scenario,
|
|
1710
1947
|
disconnect: async () => {
|
|
1711
1948
|
if (!detach) return;
|
|
1712
1949
|
const release = detach;
|
|
1713
1950
|
detach = void 0;
|
|
1951
|
+
refresh = void 0;
|
|
1714
1952
|
await release();
|
|
1715
1953
|
}
|
|
1716
1954
|
});
|
|
@@ -1721,9 +1959,11 @@ function connectAgent(options, handler) {
|
|
|
1721
1959
|
0 && (module.exports = {
|
|
1722
1960
|
AGENT_TRANSPORTS,
|
|
1723
1961
|
AgentParameterError,
|
|
1962
|
+
AgentStateError,
|
|
1724
1963
|
DEFAULT_TIMEOUT_MS,
|
|
1725
1964
|
MAX_TIMEOUT_MS,
|
|
1726
1965
|
PROTOCOL_VERSION,
|
|
1966
|
+
STATE_MAX_BYTES,
|
|
1727
1967
|
connectAgent,
|
|
1728
1968
|
normalizeReply,
|
|
1729
1969
|
parameterSpecsFromSchema,
|