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.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,14 +40,16 @@ 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 = "
|
|
47
|
+
var version = "2.0.0";
|
|
48
48
|
|
|
49
49
|
// src/internal/constants.ts
|
|
50
50
|
var LANGWATCH_SDK_VERSION = version;
|
|
51
|
+
var API_VERSION_HEADER = "X-API-Version";
|
|
52
|
+
var LANGWATCH_API_VERSION = "2026-10-26";
|
|
51
53
|
var DEFAULT_ENDPOINT = "https://app.langwatch.ai";
|
|
52
54
|
|
|
53
55
|
// src/internal/endpoint.ts
|
|
@@ -102,7 +104,9 @@ function resolveEnabled({
|
|
|
102
104
|
env = process.env
|
|
103
105
|
}) {
|
|
104
106
|
const flag = env.LANGWATCH_AGENT_CONNECT;
|
|
105
|
-
if (isSet2(flag)
|
|
107
|
+
if (isSet2(flag)) {
|
|
108
|
+
if (!isTruthy(flag)) return false;
|
|
109
|
+
}
|
|
106
110
|
if (explicit !== void 0) return explicit;
|
|
107
111
|
return !isTruthy(env.CI);
|
|
108
112
|
}
|
|
@@ -185,24 +189,37 @@ var isStringList = (value) => Array.isArray(value) && value.every(isString);
|
|
|
185
189
|
var readScope = (value) => {
|
|
186
190
|
if (!isRecord(value)) return { kind: "shared" };
|
|
187
191
|
if (value.kind === "owner") return { kind: "owner" };
|
|
188
|
-
if (value.kind === "host" && isString(value.hostLabel))
|
|
192
|
+
if (value.kind === "host" && isString(value.hostLabel))
|
|
193
|
+
return { kind: "host", hostLabel: value.hostLabel };
|
|
189
194
|
return { kind: "shared" };
|
|
190
195
|
};
|
|
196
|
+
var readRegisteredAgent = (entry) => {
|
|
197
|
+
if (!isRecord(entry)) return null;
|
|
198
|
+
if (!isString(entry.name)) return null;
|
|
199
|
+
let id = null;
|
|
200
|
+
if (isString(entry.id)) {
|
|
201
|
+
id = entry.id;
|
|
202
|
+
} else if (isString(entry.agentId)) {
|
|
203
|
+
id = entry.agentId;
|
|
204
|
+
}
|
|
205
|
+
if (id === null) return null;
|
|
206
|
+
return {
|
|
207
|
+
name: entry.name,
|
|
208
|
+
environment: isString(entry.environment) ? entry.environment : "",
|
|
209
|
+
id,
|
|
210
|
+
url: isString(entry.url) ? entry.url : "",
|
|
211
|
+
parameterNotes: isStringList(entry.parameterNotes) ? entry.parameterNotes : [],
|
|
212
|
+
scope: readScope(entry.scope)
|
|
213
|
+
};
|
|
214
|
+
};
|
|
191
215
|
var readRegistered = (frame) => {
|
|
192
|
-
if (!Array.isArray(frame.agents)
|
|
216
|
+
if (!Array.isArray(frame.agents)) return null;
|
|
217
|
+
if (!isString(frame.instanceId)) return null;
|
|
193
218
|
const agents = [];
|
|
194
219
|
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
|
-
});
|
|
220
|
+
const agent = readRegisteredAgent(entry);
|
|
221
|
+
if (agent === null) return null;
|
|
222
|
+
agents.push(agent);
|
|
206
223
|
}
|
|
207
224
|
return {
|
|
208
225
|
type: "registered",
|
|
@@ -238,12 +255,15 @@ var readDeadline = (value) => {
|
|
|
238
255
|
return null;
|
|
239
256
|
};
|
|
240
257
|
var readCall = (frame) => {
|
|
241
|
-
if (!isString(frame.callId)
|
|
258
|
+
if (!isString(frame.callId)) return null;
|
|
259
|
+
if (!isString(frame.agentId)) return null;
|
|
242
260
|
const messages = isMessageList(frame.messages) ? frame.messages : [];
|
|
243
261
|
const run = isRecord(frame.run) ? frame.run : {};
|
|
244
262
|
return {
|
|
245
263
|
type: "call",
|
|
246
264
|
protocol: typeof frame.protocol === "number" ? frame.protocol : PROTOCOL_VERSION,
|
|
265
|
+
// A platform that predates hooks sends no kind: every call is a turn.
|
|
266
|
+
kind: isString(frame.kind) ? frame.kind : "turn",
|
|
247
267
|
callId: frame.callId,
|
|
248
268
|
agentId: frame.agentId,
|
|
249
269
|
threadId: isString(frame.threadId) ? frame.threadId : "",
|
|
@@ -251,6 +271,7 @@ var readCall = (frame) => {
|
|
|
251
271
|
newMessages: isMessageList(frame.newMessages) ? frame.newMessages : messages,
|
|
252
272
|
params: readParams(frame.params),
|
|
253
273
|
session: frame.session === void 0 ? null : frame.session,
|
|
274
|
+
state: isRecord(frame.state) ? frame.state : null,
|
|
254
275
|
traceparent: isString(frame.traceparent) ? frame.traceparent : null,
|
|
255
276
|
deadlineAt: readDeadline(frame.deadlineAt),
|
|
256
277
|
run: {
|
|
@@ -267,7 +288,8 @@ function parseServerFrame(raw) {
|
|
|
267
288
|
} catch {
|
|
268
289
|
return null;
|
|
269
290
|
}
|
|
270
|
-
if (!isRecord(parsed)
|
|
291
|
+
if (!isRecord(parsed)) return null;
|
|
292
|
+
if (!isString(parsed.type)) return null;
|
|
271
293
|
switch (parsed.type) {
|
|
272
294
|
case "registered":
|
|
273
295
|
return readRegistered(parsed);
|
|
@@ -296,164 +318,6 @@ function traceIdFromTraceparent(traceparent) {
|
|
|
296
318
|
return traceId.toLowerCase();
|
|
297
319
|
}
|
|
298
320
|
|
|
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
321
|
// src/agent/transport.ts
|
|
458
322
|
import { createRequire } from "module";
|
|
459
323
|
|
|
@@ -470,11 +334,7 @@ var CREDENTIAL_HEADERS = [
|
|
|
470
334
|
"x-project-id"
|
|
471
335
|
];
|
|
472
336
|
var LangWatchRedirectError = class extends Error {
|
|
473
|
-
constructor({
|
|
474
|
-
url,
|
|
475
|
-
location,
|
|
476
|
-
status
|
|
477
|
-
}) {
|
|
337
|
+
constructor({ url, location, status }) {
|
|
478
338
|
super(
|
|
479
339
|
`LangWatch refused to follow a redirect from ${url} to ${location ?? "an unknown location"} (HTTP ${status}). Set the endpoint to the final URL.`
|
|
480
340
|
);
|
|
@@ -484,6 +344,20 @@ var LangWatchRedirectError = class extends Error {
|
|
|
484
344
|
this.status = status;
|
|
485
345
|
}
|
|
486
346
|
};
|
|
347
|
+
var isPlainHeaders = (headers) => !(headers instanceof Headers) && !Array.isArray(headers);
|
|
348
|
+
var withApiVersion = ({
|
|
349
|
+
input,
|
|
350
|
+
init
|
|
351
|
+
}) => {
|
|
352
|
+
const sent = init?.headers ?? (typeof Request !== "undefined" && input instanceof Request ? input.headers : void 0);
|
|
353
|
+
if (new Headers(sent).has(API_VERSION_HEADER)) return { ...init };
|
|
354
|
+
if (sent === void 0 || isPlainHeaders(sent)) {
|
|
355
|
+
return { ...init, headers: { ...sent, [API_VERSION_HEADER]: LANGWATCH_API_VERSION } };
|
|
356
|
+
}
|
|
357
|
+
const headers = new Headers(sent);
|
|
358
|
+
headers.set(API_VERSION_HEADER, LANGWATCH_API_VERSION);
|
|
359
|
+
return { ...init, headers };
|
|
360
|
+
};
|
|
487
361
|
var warnedOrigins = /* @__PURE__ */ new Set();
|
|
488
362
|
var isRedirect = (response) => response.type === "opaqueredirect" || REDIRECT_STATUSES.has(response.status);
|
|
489
363
|
var isStream = (body) => typeof ReadableStream !== "undefined" && body instanceof ReadableStream;
|
|
@@ -621,7 +495,19 @@ var upgrade = async ({
|
|
|
621
495
|
const location = first.headers.get("location");
|
|
622
496
|
const refused = refusalOf({ url, response: first });
|
|
623
497
|
const target = location === null ? null : schemeUpgradeTarget({ url, location });
|
|
624
|
-
if (location === null
|
|
498
|
+
if (location === null) {
|
|
499
|
+
discard(spare);
|
|
500
|
+
throw refused;
|
|
501
|
+
}
|
|
502
|
+
if (first.status === 303) {
|
|
503
|
+
discard(spare);
|
|
504
|
+
throw refused;
|
|
505
|
+
}
|
|
506
|
+
if (target === null) {
|
|
507
|
+
discard(spare);
|
|
508
|
+
throw refused;
|
|
509
|
+
}
|
|
510
|
+
if (isStream(init?.body)) {
|
|
625
511
|
discard(spare);
|
|
626
512
|
throw refused;
|
|
627
513
|
}
|
|
@@ -644,7 +530,8 @@ var createLangWatchFetch = ({
|
|
|
644
530
|
} = {}) => {
|
|
645
531
|
const send = fetchImpl ?? ((input, init) => globalThis.fetch(input, init));
|
|
646
532
|
const log = logger ?? new ConsoleLogger({ level: "warn", prefix: "LangWatch" });
|
|
647
|
-
return async (input,
|
|
533
|
+
return async (input, given) => {
|
|
534
|
+
const init = withApiVersion({ input, init: given });
|
|
648
535
|
const url = requestUrl(input);
|
|
649
536
|
const effective = effectiveRequest({ input, init });
|
|
650
537
|
const method = (effective?.method ?? init?.method ?? "GET").toUpperCase();
|
|
@@ -671,7 +558,10 @@ function resolveTransport({
|
|
|
671
558
|
env = process.env
|
|
672
559
|
}) {
|
|
673
560
|
const candidate = isSet3(explicit) ? explicit : env.LANGWATCH_AGENT_TRANSPORT;
|
|
674
|
-
|
|
561
|
+
if (!isSet3(candidate)) return "websocket";
|
|
562
|
+
const normalized = candidate.trim().toLowerCase();
|
|
563
|
+
if (normalized === "http") return "http";
|
|
564
|
+
return "websocket";
|
|
675
565
|
}
|
|
676
566
|
var NoWebSocketError = class extends Error {
|
|
677
567
|
constructor() {
|
|
@@ -768,14 +658,14 @@ var HttpLongPollSocket = class {
|
|
|
768
658
|
return;
|
|
769
659
|
}
|
|
770
660
|
if (frame.type === "ack" && typeof frame.callId === "string") this.inFlight.add(frame.callId);
|
|
771
|
-
if (frame.type === "result" && typeof frame.callId === "string")
|
|
661
|
+
if (frame.type === "result" && typeof frame.callId === "string")
|
|
662
|
+
this.inFlight.delete(frame.callId);
|
|
772
663
|
this.outbox = this.outbox.then(() => this.registered).then(() => this.post(data)).catch(() => void 0);
|
|
773
664
|
}
|
|
774
665
|
/**
|
|
775
|
-
* Stops polling, lets
|
|
776
|
-
*
|
|
777
|
-
*
|
|
778
|
-
* hold the connection open.
|
|
666
|
+
* Stops polling, lets queued frames go out, then reports close. Bounded: on
|
|
667
|
+
* the deadline the frame requests are aborted and close is reported anyway,
|
|
668
|
+
* so an unanswering request cannot hold the connection open.
|
|
779
669
|
*/
|
|
780
670
|
close(code = 1e3) {
|
|
781
671
|
this.closed = true;
|
|
@@ -830,7 +720,8 @@ var HttpLongPollSocket = class {
|
|
|
830
720
|
signal: this.polls.signal
|
|
831
721
|
});
|
|
832
722
|
} catch (error) {
|
|
833
|
-
if (!this.closed)
|
|
723
|
+
if (!this.closed)
|
|
724
|
+
this.fail(`could not reach ${this.url}/register (${describe(error)})`, 1006);
|
|
834
725
|
return;
|
|
835
726
|
}
|
|
836
727
|
const body = await this.jsonOf(response);
|
|
@@ -851,46 +742,66 @@ var HttpLongPollSocket = class {
|
|
|
851
742
|
}
|
|
852
743
|
async pollLoop() {
|
|
853
744
|
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
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
if (
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
745
|
+
if (!await this.pollOnce()) return;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
/** One long poll: delivers what it answered, and says whether polling goes on. */
|
|
749
|
+
async pollOnce() {
|
|
750
|
+
const query = this.inFlight.size > 0 ? `?inFlight=${encodeURIComponent([...this.inFlight].join(","))}` : "";
|
|
751
|
+
const startedAt = Date.now();
|
|
752
|
+
let response;
|
|
753
|
+
try {
|
|
754
|
+
response = await this.fetchImpl(`${this.url}/poll${query}`, {
|
|
755
|
+
method: "GET",
|
|
756
|
+
headers: this.requestHeaders(),
|
|
757
|
+
signal: this.polls.signal
|
|
758
|
+
});
|
|
759
|
+
} catch (error) {
|
|
760
|
+
if (!this.closed) this.fail(`the poll failed (${describe(error)})`, 1006);
|
|
761
|
+
return false;
|
|
762
|
+
}
|
|
763
|
+
if (this.closed) return false;
|
|
764
|
+
if (response.status === 410) {
|
|
765
|
+
this.fail(
|
|
766
|
+
"the platform no longer knows this instance, registering again",
|
|
767
|
+
SESSION_LOST_CLOSE_CODE
|
|
768
|
+
);
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
const body = await this.jsonOf(response);
|
|
772
|
+
if (!response.ok) {
|
|
773
|
+
this.refusePoll({ status: response.status, body });
|
|
774
|
+
return false;
|
|
775
|
+
}
|
|
776
|
+
const delivered = this.deliverFrames(body);
|
|
777
|
+
for (const listener of this.pingListeners) listener();
|
|
778
|
+
if (delivered === 0) {
|
|
779
|
+
const elapsedMs = Date.now() - startedAt;
|
|
780
|
+
if (elapsedMs < EMPTY_POLL_FLOOR_MS) await wait(EMPTY_POLL_FLOOR_MS - elapsedMs);
|
|
781
|
+
}
|
|
782
|
+
return true;
|
|
783
|
+
}
|
|
784
|
+
refusePoll({
|
|
785
|
+
status,
|
|
786
|
+
body
|
|
787
|
+
}) {
|
|
788
|
+
const answered = body && typeof body.frame === "object" && body.frame !== null ? body.frame : null;
|
|
789
|
+
if (answered) this.emitMessage(JSON.stringify(answered));
|
|
790
|
+
if (answered?.type === "refused") {
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
this.fail(`the poll was answered with HTTP ${status}`, 1006);
|
|
794
|
+
}
|
|
795
|
+
/** Emits every frame a poll answered and returns how many there were. */
|
|
796
|
+
deliverFrames(body) {
|
|
797
|
+
const frames = Array.isArray(body?.frames) ? body.frames : [];
|
|
798
|
+
for (const frame of frames) {
|
|
799
|
+
const entry = frame;
|
|
800
|
+
if (entry.type === "cancel" && typeof entry.callId === "string")
|
|
801
|
+
this.inFlight.delete(entry.callId);
|
|
802
|
+
this.emitMessage(JSON.stringify(frame));
|
|
893
803
|
}
|
|
804
|
+
return frames.length;
|
|
894
805
|
}
|
|
895
806
|
async post(data) {
|
|
896
807
|
if (!this.token) return;
|
|
@@ -908,12 +819,7 @@ var HttpLongPollSocket = class {
|
|
|
908
819
|
if (this.frames.signal.aborted) return;
|
|
909
820
|
response = null;
|
|
910
821
|
}
|
|
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;
|
|
822
|
+
if (this.postSettled(response)) return;
|
|
917
823
|
const delay = POST_RETRY_DELAYS_MS[attempt];
|
|
918
824
|
if (delay === void 0) {
|
|
919
825
|
this.fail(`a frame could not be posted after ${attempt} retries`, 1006);
|
|
@@ -922,6 +828,18 @@ var HttpLongPollSocket = class {
|
|
|
922
828
|
await wait(delay);
|
|
923
829
|
}
|
|
924
830
|
}
|
|
831
|
+
/** Whether a post's response ends its retries: delivered, refused, or the session lost. */
|
|
832
|
+
postSettled(response) {
|
|
833
|
+
if (response?.ok) return true;
|
|
834
|
+
if (response?.status === 410) {
|
|
835
|
+
this.fail(
|
|
836
|
+
"the platform no longer knows this instance, registering again",
|
|
837
|
+
SESSION_LOST_CLOSE_CODE
|
|
838
|
+
);
|
|
839
|
+
return true;
|
|
840
|
+
}
|
|
841
|
+
return response !== null && response.status < 500;
|
|
842
|
+
}
|
|
925
843
|
async jsonOf(response) {
|
|
926
844
|
try {
|
|
927
845
|
const parsed = await response.json();
|
|
@@ -975,19 +893,257 @@ function openTransportSocket({
|
|
|
975
893
|
return transport === "http" ? new HttpLongPollSocket({ url: httpUrl, headers }) : socketFactory({ url: websocketUrl, headers });
|
|
976
894
|
}
|
|
977
895
|
|
|
896
|
+
// src/agent/schema.ts
|
|
897
|
+
var AgentParameterError = class extends Error {
|
|
898
|
+
constructor(message) {
|
|
899
|
+
super(message);
|
|
900
|
+
this.code = "agent_parameter_invalid";
|
|
901
|
+
this.name = "AgentParameterError";
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
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 })`;
|
|
905
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
906
|
+
var isStandardJsonSchema = (value) => {
|
|
907
|
+
if (!isRecord2(value)) return false;
|
|
908
|
+
const standard = value["~standard"];
|
|
909
|
+
return isRecord2(standard) && isRecord2(standard.jsonSchema);
|
|
910
|
+
};
|
|
911
|
+
var isJsonSchemaObject = (value) => isRecord2(value) && value.type === "object" && isRecord2(value.properties);
|
|
912
|
+
var isDefinitionMap = (value) => isRecord2(value) && Object.values(value).every(isRecord2);
|
|
913
|
+
var definitionType = (definition) => {
|
|
914
|
+
if (definition.type) return definition.type;
|
|
915
|
+
if (definition.options) return "string";
|
|
916
|
+
const value = definition.default;
|
|
917
|
+
if (typeof value === "number") return "number";
|
|
918
|
+
if (typeof value === "boolean") return "boolean";
|
|
919
|
+
return "string";
|
|
920
|
+
};
|
|
921
|
+
var definitionMapToSchema = (definitions) => {
|
|
922
|
+
const properties = {};
|
|
923
|
+
const required = [];
|
|
924
|
+
for (const [name, definition] of Object.entries(definitions)) {
|
|
925
|
+
const property = { type: definitionType(definition) };
|
|
926
|
+
if (definition.options) property.enum = [...definition.options];
|
|
927
|
+
if (definition.default !== void 0) property.default = definition.default;
|
|
928
|
+
else required.push(name);
|
|
929
|
+
if (definition.description !== void 0) property.description = definition.description;
|
|
930
|
+
properties[name] = property;
|
|
931
|
+
}
|
|
932
|
+
const schema = { type: "object", properties };
|
|
933
|
+
if (required.length > 0) schema.required = required;
|
|
934
|
+
return schema;
|
|
935
|
+
};
|
|
936
|
+
var readStandardJsonSchema = (input) => {
|
|
937
|
+
const converter = input["~standard"].jsonSchema;
|
|
938
|
+
const options = { target: "draft-2020-12" };
|
|
939
|
+
const schema = converter.input?.(options) ?? converter.output?.(options);
|
|
940
|
+
if (schema === void 0) {
|
|
941
|
+
throw new AgentParameterError(
|
|
942
|
+
`the "~standard".jsonSchema converter has no input function; ${ACCEPTED_FORMS}`
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
if (!isRecord2(schema)) {
|
|
946
|
+
throw new AgentParameterError(
|
|
947
|
+
`the "~standard".jsonSchema converter returned no object; ${ACCEPTED_FORMS}`
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
return schema;
|
|
951
|
+
};
|
|
952
|
+
function toParameterSchema(input) {
|
|
953
|
+
if (input === void 0) return { type: "object", properties: {} };
|
|
954
|
+
if (isStandardJsonSchema(input)) return readStandardJsonSchema(input);
|
|
955
|
+
if (isJsonSchemaObject(input)) return input;
|
|
956
|
+
if (isDefinitionMap(input)) return definitionMapToSchema(input);
|
|
957
|
+
throw new AgentParameterError(ACCEPTED_FORMS);
|
|
958
|
+
}
|
|
959
|
+
var specType = (property) => {
|
|
960
|
+
const type = Array.isArray(property.type) ? property.type.find((item) => item !== "null") : property.type;
|
|
961
|
+
if (type === "number" || type === "integer") return "number";
|
|
962
|
+
if (type === "boolean") return "boolean";
|
|
963
|
+
return "string";
|
|
964
|
+
};
|
|
965
|
+
var scalar = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : void 0;
|
|
966
|
+
function parameterSpecsFromSchema(schema) {
|
|
967
|
+
const properties = isRecord2(schema.properties) ? schema.properties : {};
|
|
968
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
969
|
+
const specs = [];
|
|
970
|
+
for (const [name, raw] of Object.entries(properties)) {
|
|
971
|
+
const property = isRecord2(raw) ? raw : {};
|
|
972
|
+
const spec = { name, type: specType(property) };
|
|
973
|
+
const options = Array.isArray(property.enum) ? property.enum.filter((item) => typeof item === "string") : void 0;
|
|
974
|
+
if (options && options.length > 0) spec.options = options;
|
|
975
|
+
const fallback = scalar(property.default);
|
|
976
|
+
if (fallback !== void 0) spec.default = fallback;
|
|
977
|
+
if (typeof property.description === "string") spec.description = property.description;
|
|
978
|
+
spec.required = fallback === void 0 && required.has(name);
|
|
979
|
+
specs.push(spec);
|
|
980
|
+
}
|
|
981
|
+
return specs;
|
|
982
|
+
}
|
|
983
|
+
var coerce = ({
|
|
984
|
+
spec,
|
|
985
|
+
value
|
|
986
|
+
}) => {
|
|
987
|
+
if (spec.type === "number") {
|
|
988
|
+
return coerceNumber(spec.name, value);
|
|
989
|
+
}
|
|
990
|
+
if (spec.type === "boolean") {
|
|
991
|
+
if (typeof value === "boolean") return value;
|
|
992
|
+
if (value === "true") return true;
|
|
993
|
+
if (value === "false") return false;
|
|
994
|
+
throw new AgentParameterError(
|
|
995
|
+
`parameter "${spec.name}" must be true or false, got ${JSON.stringify(value)}`
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
const asString = typeof value === "string" ? value : String(value);
|
|
999
|
+
if (spec.options && !spec.options.includes(asString)) {
|
|
1000
|
+
throw new AgentParameterError(
|
|
1001
|
+
`parameter "${spec.name}" must be one of ${spec.options.join(", ")}, got ${JSON.stringify(value)}`
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
return asString;
|
|
1005
|
+
};
|
|
1006
|
+
function resolveParameterValues({
|
|
1007
|
+
specs,
|
|
1008
|
+
supplied
|
|
1009
|
+
}) {
|
|
1010
|
+
const values = { ...supplied };
|
|
1011
|
+
for (const spec of specs) {
|
|
1012
|
+
const value = values[spec.name];
|
|
1013
|
+
if (value === void 0) {
|
|
1014
|
+
if (spec.default !== void 0) {
|
|
1015
|
+
values[spec.name] = spec.default;
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
if (!spec.required) continue;
|
|
1019
|
+
throw new AgentParameterError(
|
|
1020
|
+
`parameter "${spec.name}" is required and the run did not supply it`
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
values[spec.name] = coerce({ spec, value });
|
|
1024
|
+
}
|
|
1025
|
+
return values;
|
|
1026
|
+
}
|
|
1027
|
+
var issuePath = (issue) => (issue.path ?? []).map(
|
|
1028
|
+
(segment) => typeof segment === "object" && segment !== null ? String(segment.key) : String(segment)
|
|
1029
|
+
).join(".");
|
|
1030
|
+
async function validateParameterValues({
|
|
1031
|
+
schema,
|
|
1032
|
+
values
|
|
1033
|
+
}) {
|
|
1034
|
+
const standard = schema["~standard"];
|
|
1035
|
+
const result = await standard.validate?.(values);
|
|
1036
|
+
if (result === void 0) return values;
|
|
1037
|
+
if (result.issues) {
|
|
1038
|
+
const detail = result.issues.map((issue) => {
|
|
1039
|
+
const path2 = issuePath(issue);
|
|
1040
|
+
return path2 ? `${path2}: ${issue.message}` : issue.message;
|
|
1041
|
+
}).join("; ");
|
|
1042
|
+
throw new AgentParameterError(`parameters refused by the schema: ${detail}`);
|
|
1043
|
+
}
|
|
1044
|
+
const parsed = isRecord2(result.value) ? result.value : {};
|
|
1045
|
+
return { ...values, ...parsed };
|
|
1046
|
+
}
|
|
1047
|
+
function createParameterReader({
|
|
1048
|
+
input,
|
|
1049
|
+
specs
|
|
1050
|
+
}) {
|
|
1051
|
+
const schema = isStandardJsonSchema(input) ? input : void 0;
|
|
1052
|
+
return async (supplied) => {
|
|
1053
|
+
const values = resolveParameterValues({ specs, supplied });
|
|
1054
|
+
return schema ? validateParameterValues({ schema, values }) : values;
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
function coerceNumber(name, value) {
|
|
1058
|
+
const asNumber = typeof value === "number" ? value : Number(value);
|
|
1059
|
+
if (typeof value === "boolean") {
|
|
1060
|
+
throw new AgentParameterError(
|
|
1061
|
+
`parameter "${name}" must be a number, got ${JSON.stringify(value)}`
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
1064
|
+
if (!Number.isFinite(asNumber)) {
|
|
1065
|
+
throw new AgentParameterError(
|
|
1066
|
+
`parameter "${name}" must be a number, got ${JSON.stringify(value)}`
|
|
1067
|
+
);
|
|
1068
|
+
}
|
|
1069
|
+
const stringValue2 = String(value).trim();
|
|
1070
|
+
if (stringValue2 === "") {
|
|
1071
|
+
throw new AgentParameterError(
|
|
1072
|
+
`parameter "${name}" must be a number, got ${JSON.stringify(value)}`
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
return asNumber;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// src/agent/state.ts
|
|
1079
|
+
var STATE_MAX_BYTES = 64 * 1024;
|
|
1080
|
+
var AgentStateError = class extends Error {
|
|
1081
|
+
constructor(code, message) {
|
|
1082
|
+
super(message);
|
|
1083
|
+
this.code = code;
|
|
1084
|
+
this.name = "AgentStateError";
|
|
1085
|
+
}
|
|
1086
|
+
};
|
|
1087
|
+
var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1088
|
+
function checkState(value) {
|
|
1089
|
+
if (value === void 0 || value === null) return void 0;
|
|
1090
|
+
if (!isPlainObject(value)) {
|
|
1091
|
+
throw new AgentStateError(
|
|
1092
|
+
"agent_call_failed",
|
|
1093
|
+
`the state must be a JSON object, got ${Array.isArray(value) ? "an array" : typeof value}`
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
let encoded;
|
|
1097
|
+
try {
|
|
1098
|
+
encoded = JSON.stringify(value);
|
|
1099
|
+
} catch (error) {
|
|
1100
|
+
throw new AgentStateError(
|
|
1101
|
+
"agent_call_failed",
|
|
1102
|
+
`the state is not JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
const sizeBytes = Buffer.byteLength(encoded, "utf8");
|
|
1106
|
+
if (sizeBytes > STATE_MAX_BYTES) {
|
|
1107
|
+
throw new AgentStateError(
|
|
1108
|
+
"agent_payload_too_large",
|
|
1109
|
+
`The state is ${sizeBytes} bytes, above the limit of ${STATE_MAX_BYTES} bytes.`
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1112
|
+
return value;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
978
1115
|
// src/agent/client.ts
|
|
1116
|
+
var kindRefusal = (runtime, kind) => {
|
|
1117
|
+
if (kind === "turn") return null;
|
|
1118
|
+
if (kind !== "setup" && kind !== "teardown") {
|
|
1119
|
+
return { code: "agent_call_failed", message: `unknown call kind "${kind}"` };
|
|
1120
|
+
}
|
|
1121
|
+
if (runtime.hooks[kind]) return null;
|
|
1122
|
+
return {
|
|
1123
|
+
code: "hook_not_declared",
|
|
1124
|
+
message: `agent "${runtime.name}" declares no ${kind} hook`
|
|
1125
|
+
};
|
|
1126
|
+
};
|
|
1127
|
+
var errorCodeOf = (error) => error instanceof AgentParameterError || error instanceof AgentStateError ? error.code : "agent_call_failed";
|
|
1128
|
+
var answerCall = async ({
|
|
1129
|
+
runtime,
|
|
1130
|
+
frame,
|
|
1131
|
+
call
|
|
1132
|
+
}) => {
|
|
1133
|
+
const hook = frame.kind === "setup" || frame.kind === "teardown" ? runtime.hooks[frame.kind] : void 0;
|
|
1134
|
+
if (!hook) {
|
|
1135
|
+
const result = await runtime.run(call);
|
|
1136
|
+
return { ...result, state: checkState(result.state) };
|
|
1137
|
+
}
|
|
1138
|
+
const state = await hook({ run: frame.run, state: frame.state, params: call.params });
|
|
1139
|
+
return { state: checkState(frame.kind === "setup" ? state ?? {} : state) };
|
|
1140
|
+
};
|
|
979
1141
|
var FAILURE_NOTICE_INTERVAL_MS = 5 * 6e4;
|
|
980
1142
|
var NOT_CONNECTED = "not connected to LangWatch";
|
|
981
1143
|
function refusalAdvice(frame) {
|
|
982
1144
|
switch (frame.code) {
|
|
983
1145
|
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 !== "");
|
|
1146
|
+
const listed = projectNames(frame.meta?.projects);
|
|
991
1147
|
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
1148
|
}
|
|
993
1149
|
case "api_key_invalid":
|
|
@@ -1007,8 +1163,30 @@ function refusalAdvice(frame) {
|
|
|
1007
1163
|
return `${frame.message} (${frame.code})`;
|
|
1008
1164
|
}
|
|
1009
1165
|
}
|
|
1166
|
+
function projectNames(value) {
|
|
1167
|
+
if (!Array.isArray(value)) return [];
|
|
1168
|
+
return value.map(projectName).filter((name) => name !== "");
|
|
1169
|
+
}
|
|
1170
|
+
function projectName(value) {
|
|
1171
|
+
if (!isRecord3(value)) return "";
|
|
1172
|
+
const id = stringValue(value.id);
|
|
1173
|
+
const name = stringValue(value.name);
|
|
1174
|
+
if (name && id) return `${name} (${id})`;
|
|
1175
|
+
return id || name;
|
|
1176
|
+
}
|
|
1177
|
+
function isRecord3(value) {
|
|
1178
|
+
return typeof value === "object" && value !== null;
|
|
1179
|
+
}
|
|
1180
|
+
function stringValue(value) {
|
|
1181
|
+
return typeof value === "string" ? value : "";
|
|
1182
|
+
}
|
|
1010
1183
|
var SHUTDOWN_SIGNALS = ["SIGINT", "SIGTERM"];
|
|
1011
1184
|
var CLOSE_GRACE_MS = 500;
|
|
1185
|
+
var readCallTraceContext = (frame) => {
|
|
1186
|
+
const parent = frame.traceparent ? propagation.extract(context.active(), { traceparent: frame.traceparent }) : context.active();
|
|
1187
|
+
const traceId = trace.getSpanContext(parent)?.traceId ?? traceIdFromTraceparent(frame.traceparent) ?? "";
|
|
1188
|
+
return { parent, traceId };
|
|
1189
|
+
};
|
|
1012
1190
|
var AgentClient = class {
|
|
1013
1191
|
constructor(config) {
|
|
1014
1192
|
this.agents = [];
|
|
@@ -1049,7 +1227,7 @@ var AgentClient = class {
|
|
|
1049
1227
|
get isRegistered() {
|
|
1050
1228
|
return this.registered;
|
|
1051
1229
|
}
|
|
1052
|
-
/** True once the client gave up: refused, or no socket implementation
|
|
1230
|
+
/** True once the client gave up: refused, or no socket implementation available. */
|
|
1053
1231
|
get isStopped() {
|
|
1054
1232
|
return this.stopped;
|
|
1055
1233
|
}
|
|
@@ -1066,7 +1244,9 @@ var AgentClient = class {
|
|
|
1066
1244
|
addAgent(runtime) {
|
|
1067
1245
|
this.agents.push(runtime);
|
|
1068
1246
|
if (this.gaveUp) {
|
|
1069
|
-
this.logger.debug(
|
|
1247
|
+
this.logger.debug(
|
|
1248
|
+
`agent "${runtime.name}" ${NOT_CONNECTED}: the connection gave up earlier in this process`
|
|
1249
|
+
);
|
|
1070
1250
|
return;
|
|
1071
1251
|
}
|
|
1072
1252
|
this.stopped = false;
|
|
@@ -1076,6 +1256,10 @@ var AgentClient = class {
|
|
|
1076
1256
|
}
|
|
1077
1257
|
this.scheduleConnect(0);
|
|
1078
1258
|
}
|
|
1259
|
+
/** Registers the agents again after one changed: a hook declared once the socket opened. */
|
|
1260
|
+
refreshAgents() {
|
|
1261
|
+
if (this.socket) this.restartSocket();
|
|
1262
|
+
}
|
|
1079
1263
|
/** Closes the socket and connects again at once, keeping the reconnect loop. */
|
|
1080
1264
|
restartSocket() {
|
|
1081
1265
|
const socket = this.socket;
|
|
@@ -1235,7 +1419,10 @@ var AgentClient = class {
|
|
|
1235
1419
|
* connection is lost, and silence while the retries run: the same notice
|
|
1236
1420
|
* repeats only after the notice interval, and a reconnect resets it.
|
|
1237
1421
|
*/
|
|
1238
|
-
noteDisconnected({
|
|
1422
|
+
noteDisconnected({
|
|
1423
|
+
wasRegistered,
|
|
1424
|
+
code
|
|
1425
|
+
}) {
|
|
1239
1426
|
const now = Date.now();
|
|
1240
1427
|
if (wasRegistered) {
|
|
1241
1428
|
this.logger.warn(
|
|
@@ -1328,7 +1515,8 @@ var AgentClient = class {
|
|
|
1328
1515
|
this.logger.info(`connected to LangWatch over HTTP long polling at ${this.httpUrl}`);
|
|
1329
1516
|
}
|
|
1330
1517
|
this.heartbeatIntervalMs = frame.heartbeatIntervalMs;
|
|
1331
|
-
if (frame.instanceId && frame.instanceId !== this.instance.id)
|
|
1518
|
+
if (frame.instanceId && frame.instanceId !== this.instance.id)
|
|
1519
|
+
this.instance.id = frame.instanceId;
|
|
1332
1520
|
this.byId.clear();
|
|
1333
1521
|
for (const entry of frame.agents) {
|
|
1334
1522
|
const runtime = this.agents.find(
|
|
@@ -1355,21 +1543,9 @@ var AgentClient = class {
|
|
|
1355
1543
|
});
|
|
1356
1544
|
return;
|
|
1357
1545
|
}
|
|
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
|
-
});
|
|
1546
|
+
const refusal = this.callRefusal({ frame, runtime });
|
|
1547
|
+
if (refusal) {
|
|
1548
|
+
this.sendError({ callId: frame.callId, ...refusal });
|
|
1373
1549
|
return;
|
|
1374
1550
|
}
|
|
1375
1551
|
const entry = { runtime, cancelled: false, timer: null };
|
|
@@ -1392,18 +1568,18 @@ var AgentClient = class {
|
|
|
1392
1568
|
}
|
|
1393
1569
|
this.send({ type: "ack", protocol: PROTOCOL_VERSION, callId: frame.callId });
|
|
1394
1570
|
this.armCallDeadline({ frame, entry, runtime });
|
|
1395
|
-
const parent
|
|
1396
|
-
const traceId = trace.getSpanContext(parent)?.traceId ?? traceIdFromTraceparent(frame.traceparent) ?? "";
|
|
1571
|
+
const { parent, traceId } = readCallTraceContext(frame);
|
|
1397
1572
|
const call = {
|
|
1398
1573
|
messages: frame.messages,
|
|
1399
1574
|
newMessages: frame.newMessages,
|
|
1400
1575
|
threadId: frame.threadId,
|
|
1401
1576
|
session: frame.session,
|
|
1577
|
+
state: frame.state,
|
|
1402
1578
|
params,
|
|
1403
1579
|
traceId
|
|
1404
1580
|
};
|
|
1405
1581
|
try {
|
|
1406
|
-
const result = await context.with(parent, () => runtime
|
|
1582
|
+
const result = await context.with(parent, () => answerCall({ runtime, frame, call }));
|
|
1407
1583
|
if (entry.cancelled) return;
|
|
1408
1584
|
this.releaseCall({ callId: frame.callId, entry });
|
|
1409
1585
|
await this.flushSpans();
|
|
@@ -1411,12 +1587,13 @@ var AgentClient = class {
|
|
|
1411
1587
|
type: "result",
|
|
1412
1588
|
protocol: PROTOCOL_VERSION,
|
|
1413
1589
|
callId: frame.callId,
|
|
1414
|
-
output: result.output,
|
|
1415
|
-
...result.session === void 0 ? {} : { session: result.session }
|
|
1590
|
+
...result.output === void 0 ? {} : { output: result.output },
|
|
1591
|
+
...result.session === void 0 ? {} : { session: result.session },
|
|
1592
|
+
...result.state === void 0 ? {} : { state: result.state }
|
|
1416
1593
|
});
|
|
1417
1594
|
} catch (error) {
|
|
1418
1595
|
if (entry.cancelled) return;
|
|
1419
|
-
const code = error
|
|
1596
|
+
const code = errorCodeOf(error);
|
|
1420
1597
|
const message = describeError(error);
|
|
1421
1598
|
this.logger.warn(`agent "${runtime.name}" call ${frame.callId} failed: ${message}`);
|
|
1422
1599
|
this.releaseCall({ callId: frame.callId, entry });
|
|
@@ -1426,15 +1603,32 @@ var AgentClient = class {
|
|
|
1426
1603
|
this.releaseCall({ callId: frame.callId, entry });
|
|
1427
1604
|
}
|
|
1428
1605
|
}
|
|
1606
|
+
/** Why the runtime cannot take this call now, or null when it can. */
|
|
1607
|
+
callRefusal({
|
|
1608
|
+
frame,
|
|
1609
|
+
runtime
|
|
1610
|
+
}) {
|
|
1611
|
+
const kind = kindRefusal(runtime, frame.kind);
|
|
1612
|
+
if (kind) return kind;
|
|
1613
|
+
const busy = [...this.inFlight.values()].filter((call) => call.runtime === runtime).length;
|
|
1614
|
+
if (busy >= runtime.concurrency) {
|
|
1615
|
+
return {
|
|
1616
|
+
code: "agent_busy",
|
|
1617
|
+
message: `agent "${runtime.name}" has ${busy} call${busy === 1 ? "" : "s"} in flight, its limit`
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
if (frame.deadlineAt !== null && frame.deadlineAt <= Date.now()) {
|
|
1621
|
+
return {
|
|
1622
|
+
code: "agent_call_timeout",
|
|
1623
|
+
message: "the call deadline passed before it started"
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
return null;
|
|
1627
|
+
}
|
|
1429
1628
|
/**
|
|
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.
|
|
1629
|
+
* Exports the spans of the call now, since a batch exporter would
|
|
1630
|
+
* otherwise hold them for seconds after the judge reads them. Awaited
|
|
1631
|
+
* before the result or error frame goes out.
|
|
1438
1632
|
*/
|
|
1439
1633
|
async flushSpans() {
|
|
1440
1634
|
const provider = trace.getTracerProvider();
|
|
@@ -1466,26 +1660,33 @@ var AgentClient = class {
|
|
|
1466
1660
|
runtime
|
|
1467
1661
|
}) {
|
|
1468
1662
|
const fromDeadline = frame.deadlineAt === null ? Infinity : frame.deadlineAt - Date.now();
|
|
1469
|
-
const limit = Math.min(fromDeadline, runtime.timeoutMs);
|
|
1663
|
+
const limit = frame.kind !== "turn" && frame.deadlineAt !== null ? fromDeadline : Math.min(fromDeadline, runtime.timeoutMs);
|
|
1470
1664
|
if (!Number.isFinite(limit)) return;
|
|
1471
|
-
const timer = setTimeout(
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1665
|
+
const timer = setTimeout(
|
|
1666
|
+
() => {
|
|
1667
|
+
entry.timer = null;
|
|
1668
|
+
if (entry.cancelled) return;
|
|
1669
|
+
entry.cancelled = true;
|
|
1670
|
+
this.releaseCall({ callId: frame.callId, entry });
|
|
1671
|
+
this.logger.warn(
|
|
1672
|
+
`agent "${runtime.name}" call ${frame.callId} passed its ${limit} ms limit`
|
|
1673
|
+
);
|
|
1674
|
+
this.sendError({
|
|
1675
|
+
callId: frame.callId,
|
|
1676
|
+
code: "agent_call_timeout",
|
|
1677
|
+
message: `the call passed the ${limit} ms limit of agent "${runtime.name}"`
|
|
1678
|
+
});
|
|
1679
|
+
},
|
|
1680
|
+
Math.max(0, limit)
|
|
1681
|
+
);
|
|
1485
1682
|
timer.unref();
|
|
1486
1683
|
entry.timer = timer;
|
|
1487
1684
|
}
|
|
1488
|
-
sendError({
|
|
1685
|
+
sendError({
|
|
1686
|
+
callId,
|
|
1687
|
+
code,
|
|
1688
|
+
message
|
|
1689
|
+
}) {
|
|
1489
1690
|
this.send({ type: "result", protocol: PROTOCOL_VERSION, callId, error: { code, message } });
|
|
1490
1691
|
}
|
|
1491
1692
|
guard(action) {
|
|
@@ -1528,7 +1729,11 @@ var installShutdownHooks = () => {
|
|
|
1528
1729
|
}
|
|
1529
1730
|
process.on("beforeExit", onBeforeExit);
|
|
1530
1731
|
};
|
|
1531
|
-
function warnOnce2({
|
|
1732
|
+
function warnOnce2({
|
|
1733
|
+
logger,
|
|
1734
|
+
key,
|
|
1735
|
+
message
|
|
1736
|
+
}) {
|
|
1532
1737
|
if (noticesGiven.has(key)) {
|
|
1533
1738
|
logger.debug(message);
|
|
1534
1739
|
return;
|
|
@@ -1571,22 +1776,33 @@ var DEFAULT_CONCURRENCY = 10;
|
|
|
1571
1776
|
var isMessage = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.role === "string";
|
|
1572
1777
|
function normalizeReply(reply) {
|
|
1573
1778
|
if (typeof reply === "string") return { output: reply };
|
|
1574
|
-
if (Array.isArray(reply)
|
|
1779
|
+
if (Array.isArray(reply)) {
|
|
1780
|
+
if (reply.every(isMessage)) return { output: reply };
|
|
1781
|
+
}
|
|
1575
1782
|
if (isMessage(reply)) return { output: reply };
|
|
1576
1783
|
if (typeof reply === "object" && reply !== null && "output" in reply) {
|
|
1577
|
-
const { output, session } = reply;
|
|
1578
|
-
|
|
1579
|
-
|
|
1784
|
+
const { output, session, state } = reply;
|
|
1785
|
+
return {
|
|
1786
|
+
output: normalizeReply(output).output,
|
|
1787
|
+
...session === void 0 ? {} : { session },
|
|
1788
|
+
...state === void 0 ? {} : { state }
|
|
1789
|
+
};
|
|
1580
1790
|
}
|
|
1581
1791
|
throw new Error(
|
|
1582
|
-
"the agent handler must return a string, a message, a list of messages, or { output, session }"
|
|
1792
|
+
"the agent handler must return a string, a message, a list of messages, or { output, session, state }"
|
|
1583
1793
|
);
|
|
1584
1794
|
}
|
|
1585
|
-
var clampTimeout = ({
|
|
1795
|
+
var clampTimeout = ({
|
|
1796
|
+
timeoutMs,
|
|
1797
|
+
logger,
|
|
1798
|
+
name
|
|
1799
|
+
}) => {
|
|
1586
1800
|
if (timeoutMs === void 0) return DEFAULT_TIMEOUT_MS;
|
|
1587
1801
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return DEFAULT_TIMEOUT_MS;
|
|
1588
1802
|
if (timeoutMs > MAX_TIMEOUT_MS) {
|
|
1589
|
-
logger.warn(
|
|
1803
|
+
logger.warn(
|
|
1804
|
+
`agent "${name}": timeoutMs ${timeoutMs} is above the ${MAX_TIMEOUT_MS} cap, using the cap`
|
|
1805
|
+
);
|
|
1590
1806
|
return MAX_TIMEOUT_MS;
|
|
1591
1807
|
}
|
|
1592
1808
|
return Math.floor(timeoutMs);
|
|
@@ -1617,10 +1833,12 @@ function connectAgent(options, handler) {
|
|
|
1617
1833
|
newMessages: call.newMessages ?? call.messages,
|
|
1618
1834
|
threadId: call.threadId ?? `local_${Date.now().toString(36)}`,
|
|
1619
1835
|
session: call.session ?? null,
|
|
1836
|
+
state: call.state ?? null,
|
|
1620
1837
|
params,
|
|
1621
1838
|
traceId: call.traceId ?? ""
|
|
1622
1839
|
});
|
|
1623
1840
|
};
|
|
1841
|
+
const hooks = {};
|
|
1624
1842
|
const runtime = {
|
|
1625
1843
|
name,
|
|
1626
1844
|
environment,
|
|
@@ -1630,14 +1848,29 @@ function connectAgent(options, handler) {
|
|
|
1630
1848
|
parameters,
|
|
1631
1849
|
concurrency,
|
|
1632
1850
|
timeoutMs,
|
|
1633
|
-
...options.sticky ? { sticky: true } : {}
|
|
1851
|
+
...options.sticky ? { sticky: true } : {},
|
|
1852
|
+
hooks: { setup: false, teardown: false }
|
|
1634
1853
|
},
|
|
1635
1854
|
readParams: readParams2,
|
|
1636
1855
|
concurrency,
|
|
1637
1856
|
timeoutMs,
|
|
1638
|
-
run: runHandler
|
|
1857
|
+
run: runHandler,
|
|
1858
|
+
hooks
|
|
1639
1859
|
};
|
|
1640
1860
|
let detach;
|
|
1861
|
+
let refresh;
|
|
1862
|
+
const scenario = {
|
|
1863
|
+
setup: (hook) => {
|
|
1864
|
+
hooks.setup = async ({ run, params }) => hook(run, params);
|
|
1865
|
+
runtime.register.hooks = { setup: true, teardown: Boolean(hooks.teardown) };
|
|
1866
|
+
refresh?.();
|
|
1867
|
+
},
|
|
1868
|
+
teardown: (hook) => {
|
|
1869
|
+
hooks.teardown = async ({ run, state, params }) => hook(run, state, params);
|
|
1870
|
+
runtime.register.hooks = { setup: Boolean(hooks.setup), teardown: true };
|
|
1871
|
+
refresh?.();
|
|
1872
|
+
}
|
|
1873
|
+
};
|
|
1641
1874
|
if (!resolveEnabled({ explicit: options.enabled })) {
|
|
1642
1875
|
logger.debug(`agent "${name}" not connected to LangWatch: the connection is disabled`);
|
|
1643
1876
|
} else {
|
|
@@ -1659,15 +1892,18 @@ function connectAgent(options, handler) {
|
|
|
1659
1892
|
});
|
|
1660
1893
|
client.addAgent(runtime);
|
|
1661
1894
|
detach = () => client.removeAgent(runtime);
|
|
1895
|
+
refresh = () => client.refreshAgents();
|
|
1662
1896
|
}
|
|
1663
1897
|
}
|
|
1664
1898
|
const connected = Object.assign(invoke, {
|
|
1665
1899
|
environment,
|
|
1666
1900
|
parameters,
|
|
1901
|
+
scenario,
|
|
1667
1902
|
disconnect: async () => {
|
|
1668
1903
|
if (!detach) return;
|
|
1669
1904
|
const release = detach;
|
|
1670
1905
|
detach = void 0;
|
|
1906
|
+
refresh = void 0;
|
|
1671
1907
|
await release();
|
|
1672
1908
|
}
|
|
1673
1909
|
});
|
|
@@ -1677,9 +1913,11 @@ function connectAgent(options, handler) {
|
|
|
1677
1913
|
export {
|
|
1678
1914
|
AGENT_TRANSPORTS,
|
|
1679
1915
|
AgentParameterError,
|
|
1916
|
+
AgentStateError,
|
|
1680
1917
|
DEFAULT_TIMEOUT_MS,
|
|
1681
1918
|
MAX_TIMEOUT_MS,
|
|
1682
1919
|
PROTOCOL_VERSION,
|
|
1920
|
+
STATE_MAX_BYTES,
|
|
1683
1921
|
connectAgent,
|
|
1684
1922
|
normalizeReply,
|
|
1685
1923
|
parameterSpecsFromSchema,
|