@economic/agents 2.14.3 → 2.15.1
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 +28 -18
- package/dist/index.d.mts +79 -32
- package/dist/index.mjs +252 -63
- package/dist/providers.d.mts +5 -0
- package/dist/providers.mjs +5 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -651,30 +651,41 @@ When configured:
|
|
|
651
651
|
AI telemetry is emitted per turn from the `ai` SDK's OpenTelemetry spans:
|
|
652
652
|
|
|
653
653
|
- **Audit logs** → one JSON object per turn in `AGENTS_AUDIT_LOGS` (model, actor, IP, prompt, response, tool calls).
|
|
654
|
-
- **Analytics** → typed per-turn and per-tool events
|
|
655
|
-
|
|
654
|
+
- **Analytics** → typed per-turn and per-tool events, written to `AGENTS_ANALYTICS` using the
|
|
655
|
+
existing Analytics Engine schema.
|
|
656
|
+
- **Tracing** → completed turns and message feedback, handed to the handlers you return from
|
|
657
|
+
`getTracingHandlers()`.
|
|
656
658
|
|
|
657
|
-
|
|
658
|
-
`
|
|
659
|
+
Override `getTracingHandlers()` to send turns and message feedback to a tracing or analytics
|
|
660
|
+
service. Each handler's `handle` receives a `TracingEvent`, a `turn` or a `feedback` event told
|
|
661
|
+
apart by `type`. Both carry their OpenTelemetry `spans`, so a handler that only forwards spans
|
|
662
|
+
doesn't need to check:
|
|
659
663
|
|
|
660
664
|
```typescript
|
|
661
|
-
import type {
|
|
665
|
+
import type { TracingHandler } from "@economic/agents";
|
|
662
666
|
|
|
663
|
-
const
|
|
664
|
-
name: "
|
|
665
|
-
|
|
666
|
-
await sendToWarehouse(event);
|
|
667
|
-
},
|
|
667
|
+
const tracing: TracingHandler = {
|
|
668
|
+
name: "tracing-service",
|
|
669
|
+
handle: (event) => sendToTracingService(event.spans),
|
|
668
670
|
};
|
|
669
671
|
|
|
670
|
-
|
|
671
|
-
return [
|
|
672
|
+
getTracingHandlers() {
|
|
673
|
+
return [tracing];
|
|
672
674
|
}
|
|
673
675
|
```
|
|
674
676
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
677
|
+
A `turn` event comes when a turn ends: who it was for, its request `context` (as tools see it) and
|
|
678
|
+
`spans`, the `ai` SDK's OpenTelemetry spans under the turn's `ai.streamText` span. A turn
|
|
679
|
+
continuing an earlier one, e.g. after a tool approval, sits under that turn in the same trace. A
|
|
680
|
+
`feedback` event comes when a user rates an assistant message: `messageId`, `rating`, `comment` and
|
|
681
|
+
`spans`, the rating as a `gen_ai.evaluation.*` span under the turn that first answered the message
|
|
682
|
+
(none when the rating was removed).
|
|
683
|
+
Hand the spans to any OpenTelemetry exporter or span processor as they are; `examples/assistant`
|
|
684
|
+
has a complete handler.
|
|
685
|
+
|
|
686
|
+
Spans and `context` can hold prompts, tool results and credentials, so pick what you send. Handlers
|
|
687
|
+
run in the background; a failure is logged as `agent.tracing.handler.failed` and never affects the
|
|
688
|
+
chat.
|
|
678
689
|
|
|
679
690
|
Separately, the SDK writes structured operational logs for completed/failed LLM and tool calls, turn duration, framework failures, and selected recovery warnings. Prompt, response, tool argument, and tool result content is excluded. These records use the stable fields `source`, `event`, `level`, `timestamp`, `agentName`, `agentClass`, `durableObjectName`, and `attributes`, while preserving Cloudflare's diagnostic events.
|
|
680
691
|
|
|
@@ -703,8 +714,7 @@ Imported from `@economic/agents` unless noted.
|
|
|
703
714
|
| `ToolContext` | `RequestContext & { _userContext?: UserContext }`. |
|
|
704
715
|
| `AgentEnv` | The bindings the runtime expects. |
|
|
705
716
|
| `AgentConnectionState` / `AgentConnectionStatus` / `AgentConnectionType` | Connection state shared with the client. |
|
|
706
|
-
| `
|
|
707
|
-
| `createAnalyticsEngineHandler(dataset)` | Analytics Engine destination using the existing v2 positional schema. |
|
|
717
|
+
| `TracingHandler` / `TracingEvent` | Consumer-defined destinations for completed turns and message feedback. |
|
|
708
718
|
|
|
709
719
|
### Members
|
|
710
720
|
|
|
@@ -714,7 +724,7 @@ Imported from `@economic/agents` unless noted.
|
|
|
714
724
|
| `getSystemPrompt(ctx?)` | `Agent` | Required. System prompt. |
|
|
715
725
|
| `getTools()` | `Agent` | Always-on tools (default `{}`). |
|
|
716
726
|
| `getSkills()` | `Agent` | Local skills (default `[]`). |
|
|
717
|
-
| `
|
|
727
|
+
| `getTracingHandlers()` | `Agent` | Destinations for completed turns and message feedback (default: `[]`). |
|
|
718
728
|
| `getAnalyticsAttributes()` | `Agent` | Additional dimensions included on every analytics event. |
|
|
719
729
|
| `static getJwtAuthConfig(env)` | `Agent` | Optional JWT verification and actor binding. |
|
|
720
730
|
| `getUserContext(jwtToken)` | `Agent` | Optional per-user context after auth. |
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Agent as Agent$1, AgentOptions, Connection, ConnectionContext, SubAgentClass } from "agents";
|
|
2
2
|
import { Action as Action$1, ActionApprovalPolicy, ActionConfig, ActionContext as ActionContext$1, ActionIdempotencyKey, ActionPermissionSpec, ChatResponseResult, PrepareStepContext, Session, Think, ToolCallContext, ToolCallDecision, TurnConfig, TurnContext, skills } from "@cloudflare/think";
|
|
3
3
|
import { FlexibleSchema, JSONValue, LanguageModel, ModelMessage, Tool as Tool$1, ToolExecutionOptions, UIMessage } from "ai";
|
|
4
|
+
import { ReadableSpan } from "@opentelemetry/sdk-trace-base";
|
|
4
5
|
import { Observability } from "agents/observability";
|
|
5
6
|
import { createCompactFunction } from "agents/experimental/memory/utils";
|
|
6
7
|
import { SkillSource as SkillSource$1 } from "agents/skills";
|
|
@@ -114,6 +115,76 @@ type Tool<Context extends Record<string, unknown> = Record<string, unknown>, INP
|
|
|
114
115
|
};
|
|
115
116
|
declare function tool<Context extends Record<string, unknown> = Record<string, unknown>, INPUT extends JSONValue | unknown | never = any, OUTPUT extends JSONValue | unknown | never = any>(definition: Tool<Context, INPUT, OUTPUT>): Tool$1<INPUT, OUTPUT>;
|
|
116
117
|
//#endregion
|
|
118
|
+
//#region src/server/features/telemetry/exchange.d.ts
|
|
119
|
+
interface SpanIds {
|
|
120
|
+
traceId: string;
|
|
121
|
+
spanId: string;
|
|
122
|
+
}
|
|
123
|
+
interface MessageRating {
|
|
124
|
+
messageId: string;
|
|
125
|
+
/** 1 thumbs up, -1 thumbs down, 0 rating removed. */
|
|
126
|
+
rating: 1 | -1 | 0;
|
|
127
|
+
comment?: string;
|
|
128
|
+
}
|
|
129
|
+
interface TurnExchange extends SpanIds {
|
|
130
|
+
/** An earlier turn already answered the user message, e.g. before a tool approval. */
|
|
131
|
+
continues: boolean;
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/server/features/telemetry/tracing.d.ts
|
|
135
|
+
interface TracingIdentity {
|
|
136
|
+
agentName: string;
|
|
137
|
+
/** The Durable Object — the chat, for a `ChatAgent`. */
|
|
138
|
+
durableObjectName: string;
|
|
139
|
+
actorId: string;
|
|
140
|
+
}
|
|
141
|
+
/** A completed turn and the OpenTelemetry spans the `ai` SDK recorded for it. */
|
|
142
|
+
interface TracedTurn extends TracingIdentity {
|
|
143
|
+
type: "turn";
|
|
144
|
+
/** The turn's request context, as tools receive it. Can hold credentials. */
|
|
145
|
+
context: Readonly<Record<string, unknown>>;
|
|
146
|
+
/**
|
|
147
|
+
* The turn's `ai.streamText` span and, under it, its model and tool calls. A
|
|
148
|
+
* turn continuing an earlier one, e.g. after a tool approval, sits under that
|
|
149
|
+
* turn's span in the same trace.
|
|
150
|
+
*/
|
|
151
|
+
spans: readonly ReadableSpan[];
|
|
152
|
+
}
|
|
153
|
+
/** A user's rating of an assistant message, given any time after the turn. */
|
|
154
|
+
interface TracedFeedback extends TracingIdentity, MessageRating {
|
|
155
|
+
type: "feedback";
|
|
156
|
+
/**
|
|
157
|
+
* The rating as a `gen_ai.evaluation.*` span under the turn that first
|
|
158
|
+
* answered the message's user message. Empty when the rating was removed.
|
|
159
|
+
*/
|
|
160
|
+
spans: readonly ReadableSpan[];
|
|
161
|
+
}
|
|
162
|
+
type TracingEvent = TracedTurn | TracedFeedback;
|
|
163
|
+
/** Receives completed turns and message feedback, e.g. for a tracing service. */
|
|
164
|
+
interface TracingHandler {
|
|
165
|
+
readonly name?: string;
|
|
166
|
+
handle(event: TracingEvent): void | Promise<void>;
|
|
167
|
+
}
|
|
168
|
+
/** What a turn's tracer needs: where its spans go and the exchange it belongs to. */
|
|
169
|
+
interface TurnTracing {
|
|
170
|
+
onTurnSpans(spans: readonly ReadableSpan[]): void;
|
|
171
|
+
exchange?: TurnExchange;
|
|
172
|
+
}
|
|
173
|
+
interface TracingOptions {
|
|
174
|
+
getIdentity(): TracingIdentity;
|
|
175
|
+
getHandlers(): readonly TracingHandler[];
|
|
176
|
+
keepAlive<T>(callback: () => Promise<T>): Promise<T>;
|
|
177
|
+
}
|
|
178
|
+
/** Hands completed turns and message ratings to an agent's tracing handlers, in the background. */
|
|
179
|
+
declare class Tracing {
|
|
180
|
+
private readonly options;
|
|
181
|
+
constructor(options: TracingOptions);
|
|
182
|
+
/** Nothing when no handlers are configured. */
|
|
183
|
+
turn(context: Readonly<Record<string, unknown>>, messages: readonly UIMessage[]): Promise<TurnTracing | undefined>;
|
|
184
|
+
feedback(messages: readonly UIMessage[], rating: MessageRating): void;
|
|
185
|
+
private sendFeedback;
|
|
186
|
+
}
|
|
187
|
+
//#endregion
|
|
117
188
|
//#region src/server/features/analytics/types.d.ts
|
|
118
189
|
type AnalyticsAttribute = string | number | boolean;
|
|
119
190
|
type AnalyticsAttributes = Readonly<Record<string, AnalyticsAttribute>>;
|
|
@@ -163,20 +234,6 @@ type AnalyticsEventPayload = {
|
|
|
163
234
|
messageId: string;
|
|
164
235
|
rating: 1 | -1 | 0;
|
|
165
236
|
};
|
|
166
|
-
type AnalyticsEvent = AnalyticsEventPayload & {
|
|
167
|
-
schemaVersion: 1;
|
|
168
|
-
eventId: string;
|
|
169
|
-
timestamp: number;
|
|
170
|
-
agentName: string;
|
|
171
|
-
durableObjectName: string;
|
|
172
|
-
actorId: string;
|
|
173
|
-
attributes: AnalyticsAttributes;
|
|
174
|
-
};
|
|
175
|
-
/** Receives typed product-analytics events emitted by the SDK. */
|
|
176
|
-
interface AnalyticsHandler {
|
|
177
|
-
readonly name?: string;
|
|
178
|
-
handle(event: AnalyticsEvent): void | Promise<void>;
|
|
179
|
-
}
|
|
180
237
|
interface AnalyticsEventContext {
|
|
181
238
|
agentName: string;
|
|
182
239
|
durableObjectName: string;
|
|
@@ -184,28 +241,16 @@ interface AnalyticsEventContext {
|
|
|
184
241
|
attributes?: AnalyticsAttributes;
|
|
185
242
|
}
|
|
186
243
|
//#endregion
|
|
187
|
-
//#region src/server/features/analytics/analytics-engine.d.ts
|
|
188
|
-
/**
|
|
189
|
-
* Creates a Cloudflare Analytics Engine destination for SDK analytics events.
|
|
190
|
-
* It preserves the SDK v2 positional schema and never writes prompt content.
|
|
191
|
-
*/
|
|
192
|
-
declare function createAnalyticsEngineHandler(dataset: AnalyticsEngineDataset): AnalyticsHandler;
|
|
193
|
-
//#endregion
|
|
194
|
-
//#region src/server/features/analytics/dispatcher.d.ts
|
|
195
|
-
declare function createAnalyticsEvent(context: AnalyticsEventContext, payload: AnalyticsEventPayload): AnalyticsEvent;
|
|
196
|
-
declare function dispatchAnalyticsEvent(handlers: readonly AnalyticsHandler[], event: AnalyticsEvent): Promise<void>;
|
|
197
|
-
//#endregion
|
|
198
244
|
//#region src/server/features/analytics/emitter.d.ts
|
|
199
245
|
interface AnalyticsOptions {
|
|
200
246
|
getContext(): AnalyticsEventContext;
|
|
201
|
-
|
|
247
|
+
/** Where events are written; analytics is off without it. */
|
|
248
|
+
getDataset(): AnalyticsEngineDataset | undefined;
|
|
202
249
|
keepAlive<T>(callback: () => Promise<T>): Promise<T>;
|
|
203
250
|
}
|
|
204
251
|
declare class Analytics {
|
|
205
252
|
private readonly options;
|
|
206
|
-
private handlers?;
|
|
207
253
|
constructor(options: AnalyticsOptions);
|
|
208
|
-
private getHandlers;
|
|
209
254
|
private emit;
|
|
210
255
|
get enabled(): boolean;
|
|
211
256
|
get context(): AnalyticsEventContext;
|
|
@@ -303,6 +348,7 @@ declare abstract class Agent<RequestContext extends Record<string, unknown> = Re
|
|
|
303
348
|
userContext?: UserContext;
|
|
304
349
|
}): Promise<TurnSnapshot>;
|
|
305
350
|
workspaceBash: boolean;
|
|
351
|
+
sendReasoning: boolean;
|
|
306
352
|
getSkillScriptRunner(): skills.SkillScriptRunner | null;
|
|
307
353
|
protected pruneTurnMessages(messages: ModelMessage[]): ModelMessage[];
|
|
308
354
|
private _prepareTools;
|
|
@@ -419,11 +465,12 @@ declare abstract class Agent<RequestContext extends Record<string, unknown> = Re
|
|
|
419
465
|
*/
|
|
420
466
|
protected registerInstanceActivity(): Promise<void>;
|
|
421
467
|
protected readonly analytics: Analytics;
|
|
468
|
+
protected readonly tracing: Tracing;
|
|
422
469
|
/**
|
|
423
|
-
*
|
|
424
|
-
*
|
|
470
|
+
* Destinations for completed turns and message feedback, e.g. a tracing
|
|
471
|
+
* service. They run in the background; a failing one never affects the chat.
|
|
425
472
|
*/
|
|
426
|
-
|
|
473
|
+
getTracingHandlers(): readonly TracingHandler[];
|
|
427
474
|
/** Business dimensions added to every analytics event. */
|
|
428
475
|
getAnalyticsAttributes(): AnalyticsAttributes;
|
|
429
476
|
observability?: Observability;
|
|
@@ -594,4 +641,4 @@ declare function routeAgentRequest<Env>(request: Request, env: Env, options?: Ag
|
|
|
594
641
|
//#region src/server/index.d.ts
|
|
595
642
|
declare function getCurrentToolContext(): any;
|
|
596
643
|
//#endregion
|
|
597
|
-
export { type Action, type ActionContext, Agent, type AgentConnectionStatus, type AgentConnectionType, type AgentEnv, type AnalyticsAttribute, type AnalyticsAttributes,
|
|
644
|
+
export { type Action, type ActionContext, Agent, type AgentConnectionStatus, type AgentConnectionType, type AgentEnv, type AnalyticsAttribute, type AnalyticsAttributes, Assistant, ChatAgent, type ExchangedToken, type InvocableSkill, type JwtAuthConfig, type RegisteredAction, type Skill, type SkillSource, type TokenExchangeConfig, type TokenExchangeResult, type Tool, type ToolContext, type ToolSet, type TracedFeedback, type TracedTurn, type TracingEvent, type TracingHandler, type TurnSnapshot, type UnauthorizedPolicy, type UnauthorizedToolPolicy, action, createJwtAuthConfig, exchangeToken, getCurrentToolContext, routeAgentRequest, skill, tool };
|
package/dist/index.mjs
CHANGED
|
@@ -2,7 +2,8 @@ import { Agent as Agent$1, callable, getCurrentAgent, routeAgentRequest as route
|
|
|
2
2
|
import { Think, action as action$1, skills } from "@cloudflare/think";
|
|
3
3
|
import { Output, convertToModelMessages, generateText, jsonSchema, pruneMessages, tool as tool$1 } from "ai";
|
|
4
4
|
import { createRemoteJWKSet, decodeJwt, errors, jwtVerify } from "jose";
|
|
5
|
-
import { BasicTracerProvider, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
5
|
+
import { BasicTracerProvider, InMemorySpanExporter, RandomIdGenerator, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
6
|
+
import { ROOT_CONTEXT, TraceFlags, context, trace } from "@opentelemetry/api";
|
|
6
7
|
import { genericObservability } from "agents/observability";
|
|
7
8
|
import { applyToolUpdate, toolApprovalUpdate } from "agents/chat";
|
|
8
9
|
import { createCompactFunction, estimateMessageTokens, estimateStringTokens, sanitizeToolPairs, truncateOlderMessages } from "agents/experimental/memory/utils";
|
|
@@ -584,6 +585,119 @@ function handleAnalyticsSpan(span, analytics) {
|
|
|
584
585
|
});
|
|
585
586
|
}
|
|
586
587
|
//#endregion
|
|
588
|
+
//#region src/server/features/telemetry/exchange.ts
|
|
589
|
+
async function getExchangeSpanIds(chatName, userMessageId) {
|
|
590
|
+
const bytes = new TextEncoder().encode(`${chatName}:${userMessageId}`);
|
|
591
|
+
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
|
|
592
|
+
const hex = Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
593
|
+
return {
|
|
594
|
+
traceId: hex.slice(0, 32),
|
|
595
|
+
spanId: hex.slice(32, 48)
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* The exchange a turn belongs to: the last user message. The first turn
|
|
600
|
+
* answering it is the root of the exchange's trace; later turns, e.g. after a
|
|
601
|
+
* tool approval, continue that trace under it.
|
|
602
|
+
*/
|
|
603
|
+
async function getTurnExchange(chatName, messages) {
|
|
604
|
+
const userMessage = messages.findLast((message) => message.role === "user");
|
|
605
|
+
if (!userMessage) return;
|
|
606
|
+
return {
|
|
607
|
+
...await getExchangeSpanIds(chatName, userMessage.id),
|
|
608
|
+
continues: messages.at(-1) !== userMessage
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Gives a turn's `ai.streamText` span its exchange's ids. On a per-turn tracer
|
|
613
|
+
* that span is the first one started and the only one without a parent.
|
|
614
|
+
*/
|
|
615
|
+
var TurnIdGenerator = class {
|
|
616
|
+
exchange;
|
|
617
|
+
random = new RandomIdGenerator();
|
|
618
|
+
turnSpanId;
|
|
619
|
+
constructor(exchange) {
|
|
620
|
+
this.exchange = exchange;
|
|
621
|
+
this.turnSpanId = exchange.spanId;
|
|
622
|
+
}
|
|
623
|
+
generateTraceId() {
|
|
624
|
+
return this.exchange.traceId;
|
|
625
|
+
}
|
|
626
|
+
generateSpanId() {
|
|
627
|
+
const spanId = this.turnSpanId ?? this.random.generateSpanId();
|
|
628
|
+
this.turnSpanId = void 0;
|
|
629
|
+
return spanId;
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
/**
|
|
633
|
+
* A rating as a span under the turn that answered the rated message's user
|
|
634
|
+
* message, described with OpenTelemetry's GenAI evaluation attributes. None
|
|
635
|
+
* when the rating was removed or the message has no user message before it.
|
|
636
|
+
*/
|
|
637
|
+
async function createFeedbackSpan(chatName, messages, { messageId, rating, comment }) {
|
|
638
|
+
const index = messages.findIndex((message) => message.id === messageId);
|
|
639
|
+
const userMessage = messages.slice(0, Math.max(index, 0)).findLast((message) => message.role === "user");
|
|
640
|
+
if (!userMessage || rating === 0) return;
|
|
641
|
+
const exporter = new InMemorySpanExporter();
|
|
642
|
+
const provider = new BasicTracerProvider({ spanProcessors: [new SimpleSpanProcessor(exporter)] });
|
|
643
|
+
const parent = trace.setSpanContext(ROOT_CONTEXT, {
|
|
644
|
+
...await getExchangeSpanIds(chatName, userMessage.id),
|
|
645
|
+
traceFlags: TraceFlags.SAMPLED,
|
|
646
|
+
isRemote: true
|
|
647
|
+
});
|
|
648
|
+
const attributes = {
|
|
649
|
+
"gen_ai.evaluation.name": "user_feedback",
|
|
650
|
+
"gen_ai.evaluation.score.value": rating === 1 ? 1 : 0,
|
|
651
|
+
"gen_ai.evaluation.score.label": rating === 1 ? "positive" : "negative",
|
|
652
|
+
...comment ? { "gen_ai.evaluation.explanation": comment } : {}
|
|
653
|
+
};
|
|
654
|
+
provider.getTracer("@economic/agents").startSpan("user_feedback", { attributes }, parent).end();
|
|
655
|
+
await provider.forceFlush();
|
|
656
|
+
return exporter.getFinishedSpans()[0];
|
|
657
|
+
}
|
|
658
|
+
//#endregion
|
|
659
|
+
//#region src/server/features/telemetry/turn-tracer.ts
|
|
660
|
+
const TURN_SPAN_NAME = "ai.streamText";
|
|
661
|
+
/**
|
|
662
|
+
* Starts every span of a turn as a child of the turn's `ai.streamText` span.
|
|
663
|
+
*
|
|
664
|
+
* The `ai` SDK takes a span's parent from OpenTelemetry's active context, and
|
|
665
|
+
* nothing tracks that here, so each of its spans would otherwise start a trace
|
|
666
|
+
* of its own. A tracer is created per turn, so the turn span is known. With a
|
|
667
|
+
* `parent`, the turn span itself starts under that span, e.g. the first turn
|
|
668
|
+
* of the exchange it continues.
|
|
669
|
+
*/
|
|
670
|
+
var TurnTracer = class {
|
|
671
|
+
tracer;
|
|
672
|
+
parent;
|
|
673
|
+
turnSpan;
|
|
674
|
+
constructor(tracer, parent) {
|
|
675
|
+
this.tracer = tracer;
|
|
676
|
+
this.parent = parent;
|
|
677
|
+
}
|
|
678
|
+
startSpan(name, options, ctx = context.active()) {
|
|
679
|
+
if (name === TURN_SPAN_NAME) {
|
|
680
|
+
const parent = this.parent && {
|
|
681
|
+
traceId: this.parent.traceId,
|
|
682
|
+
spanId: this.parent.spanId,
|
|
683
|
+
traceFlags: TraceFlags.SAMPLED,
|
|
684
|
+
isRemote: true
|
|
685
|
+
};
|
|
686
|
+
this.turnSpan = this.tracer.startSpan(name, options, parent ? trace.setSpanContext(ctx, parent) : trace.deleteSpan(ctx));
|
|
687
|
+
return this.turnSpan;
|
|
688
|
+
}
|
|
689
|
+
const parent = this.turnSpan ? trace.setSpan(ctx, this.turnSpan) : ctx;
|
|
690
|
+
return this.tracer.startSpan(name, options, parent);
|
|
691
|
+
}
|
|
692
|
+
startActiveSpan(name, ...args) {
|
|
693
|
+
const fn = args[args.length - 1];
|
|
694
|
+
const options = args.length > 1 ? args[0] : void 0;
|
|
695
|
+
const ctx = args.length > 2 ? args[1] : context.active();
|
|
696
|
+
const span = this.startSpan(name, options, ctx);
|
|
697
|
+
return context.with(trace.setSpan(ctx, span), () => fn(span));
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
//#endregion
|
|
587
701
|
//#region src/server/features/observability/index.ts
|
|
588
702
|
const measuredEvents = /* @__PURE__ */ new Set(["chat:turn:finish"]);
|
|
589
703
|
const warningEvents = /* @__PURE__ */ new Set([
|
|
@@ -721,16 +835,20 @@ var AgentSpanExporter = class {
|
|
|
721
835
|
auditLogs;
|
|
722
836
|
analytics;
|
|
723
837
|
context;
|
|
724
|
-
|
|
838
|
+
onTurnSpans;
|
|
839
|
+
turnSpans = [];
|
|
840
|
+
constructor(auditLogs, analytics, context, onTurnSpans) {
|
|
725
841
|
this.auditLogs = auditLogs;
|
|
726
842
|
this.analytics = analytics;
|
|
727
843
|
this.context = context;
|
|
844
|
+
this.onTurnSpans = onTurnSpans;
|
|
728
845
|
}
|
|
729
846
|
export(spans, resultCallback) {
|
|
730
847
|
(async () => {
|
|
731
848
|
try {
|
|
732
849
|
for (const span of spans) {
|
|
733
850
|
logAISpan(span, this.context);
|
|
851
|
+
this.collectTurnSpan(span);
|
|
734
852
|
if (span.name === "ai.streamText.doStream") {
|
|
735
853
|
rememberDoStream(span, this.context.durableObjectName);
|
|
736
854
|
attachToolCallsToChat(span, this.context.durableObjectName);
|
|
@@ -751,9 +869,84 @@ var AgentSpanExporter = class {
|
|
|
751
869
|
})();
|
|
752
870
|
}
|
|
753
871
|
async shutdown() {}
|
|
872
|
+
collectTurnSpan(span) {
|
|
873
|
+
if (!this.onTurnSpans) return;
|
|
874
|
+
this.turnSpans.push(span);
|
|
875
|
+
if (span.name !== "ai.streamText") return;
|
|
876
|
+
const spans = this.turnSpans;
|
|
877
|
+
this.turnSpans = [];
|
|
878
|
+
this.onTurnSpans(spans);
|
|
879
|
+
}
|
|
754
880
|
};
|
|
755
|
-
function createAgentTracer(auditLogs, analytics, context) {
|
|
756
|
-
|
|
881
|
+
function createAgentTracer(auditLogs, analytics, context, tracing) {
|
|
882
|
+
const exchange = tracing?.exchange;
|
|
883
|
+
return new TurnTracer(new BasicTracerProvider({
|
|
884
|
+
...exchange && !exchange.continues ? { idGenerator: new TurnIdGenerator(exchange) } : {},
|
|
885
|
+
spanProcessors: [new SimpleSpanProcessor(new AgentSpanExporter(auditLogs, analytics, context, tracing?.onTurnSpans))]
|
|
886
|
+
}).getTracer("@economic/agents"), exchange?.continues ? exchange : void 0);
|
|
887
|
+
}
|
|
888
|
+
//#endregion
|
|
889
|
+
//#region src/server/features/telemetry/tracing.ts
|
|
890
|
+
/** Hands completed turns and message ratings to an agent's tracing handlers, in the background. */
|
|
891
|
+
var Tracing = class {
|
|
892
|
+
options;
|
|
893
|
+
constructor(options) {
|
|
894
|
+
this.options = options;
|
|
895
|
+
}
|
|
896
|
+
/** Nothing when no handlers are configured. */
|
|
897
|
+
async turn(context, messages) {
|
|
898
|
+
try {
|
|
899
|
+
const handlers = this.options.getHandlers();
|
|
900
|
+
if (handlers.length === 0) return;
|
|
901
|
+
const identity = this.options.getIdentity();
|
|
902
|
+
return {
|
|
903
|
+
exchange: await getTurnExchange(identity.durableObjectName, messages),
|
|
904
|
+
onTurnSpans: (spans) => {
|
|
905
|
+
this.options.keepAlive(() => dispatch(handlers, {
|
|
906
|
+
type: "turn",
|
|
907
|
+
...identity,
|
|
908
|
+
context,
|
|
909
|
+
spans
|
|
910
|
+
}));
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
} catch (error) {
|
|
914
|
+
logOperationalError("agent.tracing.failed", error);
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
feedback(messages, rating) {
|
|
919
|
+
this.sendFeedback(messages, rating).catch((error) => {
|
|
920
|
+
logOperationalError("agent.tracing.failed", error);
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
async sendFeedback(messages, rating) {
|
|
924
|
+
const handlers = this.options.getHandlers();
|
|
925
|
+
if (handlers.length === 0) return;
|
|
926
|
+
const identity = this.options.getIdentity();
|
|
927
|
+
await this.options.keepAlive(async () => {
|
|
928
|
+
const span = await createFeedbackSpan(identity.durableObjectName, messages, rating);
|
|
929
|
+
await dispatch(handlers, {
|
|
930
|
+
type: "feedback",
|
|
931
|
+
...identity,
|
|
932
|
+
...rating,
|
|
933
|
+
spans: span ? [span] : []
|
|
934
|
+
});
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
async function dispatch(handlers, event) {
|
|
939
|
+
await Promise.all(handlers.map(async (handler) => {
|
|
940
|
+
try {
|
|
941
|
+
await handler.handle(event);
|
|
942
|
+
} catch (error) {
|
|
943
|
+
logOperationalError("agent.tracing.handler.failed", error, {
|
|
944
|
+
agentName: event.agentName,
|
|
945
|
+
durableObjectName: event.durableObjectName,
|
|
946
|
+
attributes: { handler: handler.name ?? "handler" }
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
}));
|
|
757
950
|
}
|
|
758
951
|
//#endregion
|
|
759
952
|
//#region src/server/features/analytics/utils.ts
|
|
@@ -770,16 +963,11 @@ const MAX_INDEX_BYTES = 96;
|
|
|
770
963
|
const MAX_BLOB_BYTES = 16 * 1024;
|
|
771
964
|
const encoder = new TextEncoder();
|
|
772
965
|
/**
|
|
773
|
-
*
|
|
774
|
-
*
|
|
966
|
+
* Writes an SDK analytics event to Cloudflare Analytics Engine. It preserves
|
|
967
|
+
* the SDK v2 positional schema and never writes prompt content.
|
|
775
968
|
*/
|
|
776
|
-
function
|
|
777
|
-
|
|
778
|
-
name: "Cloudflare Analytics Engine",
|
|
779
|
-
async handle(event) {
|
|
780
|
-
for (const point of toAnalyticsEnginePoints(event)) dataset.writeDataPoint(await boundAnalyticsPoint(point));
|
|
781
|
-
}
|
|
782
|
-
};
|
|
969
|
+
async function writeAnalyticsEvent(dataset, event) {
|
|
970
|
+
for (const point of toAnalyticsEnginePoints(event)) dataset.writeDataPoint(await boundAnalyticsPoint(point));
|
|
783
971
|
}
|
|
784
972
|
async function boundAnalyticsPoint(point) {
|
|
785
973
|
return {
|
|
@@ -933,58 +1121,29 @@ function toAnalyticsEnginePoints(event) {
|
|
|
933
1121
|
}
|
|
934
1122
|
}
|
|
935
1123
|
//#endregion
|
|
936
|
-
//#region src/server/features/analytics/dispatcher.ts
|
|
937
|
-
function createAnalyticsEvent(context, payload) {
|
|
938
|
-
return {
|
|
939
|
-
...payload,
|
|
940
|
-
schemaVersion: 1,
|
|
941
|
-
eventId: crypto.randomUUID(),
|
|
942
|
-
timestamp: Date.now(),
|
|
943
|
-
agentName: context.agentName,
|
|
944
|
-
durableObjectName: context.durableObjectName,
|
|
945
|
-
actorId: context.actorId,
|
|
946
|
-
attributes: context.attributes ?? {}
|
|
947
|
-
};
|
|
948
|
-
}
|
|
949
|
-
async function dispatchAnalyticsEvent(handlers, event) {
|
|
950
|
-
await Promise.all(handlers.map(async (handler) => {
|
|
951
|
-
try {
|
|
952
|
-
await handler.handle(event);
|
|
953
|
-
} catch (error) {
|
|
954
|
-
writeOperationalLog({
|
|
955
|
-
source: "@economic/agents",
|
|
956
|
-
event: "agent.analytics.handler.failed",
|
|
957
|
-
level: "error",
|
|
958
|
-
timestamp: Date.now(),
|
|
959
|
-
agentName: event.agentName,
|
|
960
|
-
durableObjectName: event.durableObjectName,
|
|
961
|
-
attributes: {
|
|
962
|
-
handler: handler.name ?? "handler",
|
|
963
|
-
error: error instanceof Error ? error.message : String(error)
|
|
964
|
-
}
|
|
965
|
-
});
|
|
966
|
-
}
|
|
967
|
-
}));
|
|
968
|
-
}
|
|
969
|
-
//#endregion
|
|
970
1124
|
//#region src/server/features/analytics/emitter.ts
|
|
971
1125
|
var Analytics = class {
|
|
972
1126
|
options;
|
|
973
|
-
handlers;
|
|
974
1127
|
constructor(options) {
|
|
975
1128
|
this.options = options;
|
|
976
1129
|
}
|
|
977
|
-
getHandlers() {
|
|
978
|
-
return this.handlers ??= this.options.getHandlers();
|
|
979
|
-
}
|
|
980
1130
|
emit(payload) {
|
|
981
|
-
const
|
|
982
|
-
if (
|
|
983
|
-
const
|
|
984
|
-
this.options.keepAlive(() =>
|
|
1131
|
+
const dataset = this.options.getDataset();
|
|
1132
|
+
if (!dataset) return;
|
|
1133
|
+
const event = createEvent(this.context, typeof payload === "function" ? payload() : payload);
|
|
1134
|
+
this.options.keepAlive(async () => {
|
|
1135
|
+
try {
|
|
1136
|
+
await writeAnalyticsEvent(dataset, event);
|
|
1137
|
+
} catch (error) {
|
|
1138
|
+
logOperationalError("agent.analytics.write.failed", error, {
|
|
1139
|
+
agentName: event.agentName,
|
|
1140
|
+
durableObjectName: event.durableObjectName
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
});
|
|
985
1144
|
}
|
|
986
1145
|
get enabled() {
|
|
987
|
-
return this.
|
|
1146
|
+
return this.options.getDataset() !== void 0;
|
|
988
1147
|
}
|
|
989
1148
|
get context() {
|
|
990
1149
|
return this.options.getContext();
|
|
@@ -1024,6 +1183,18 @@ var Analytics = class {
|
|
|
1024
1183
|
this.emit(payload);
|
|
1025
1184
|
}
|
|
1026
1185
|
};
|
|
1186
|
+
function createEvent(context, payload) {
|
|
1187
|
+
return {
|
|
1188
|
+
...payload,
|
|
1189
|
+
schemaVersion: 1,
|
|
1190
|
+
eventId: crypto.randomUUID(),
|
|
1191
|
+
timestamp: Date.now(),
|
|
1192
|
+
agentName: context.agentName,
|
|
1193
|
+
durableObjectName: context.durableObjectName,
|
|
1194
|
+
actorId: context.actorId,
|
|
1195
|
+
attributes: context.attributes ?? {}
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1027
1198
|
//#endregion
|
|
1028
1199
|
//#region src/server/features/registry.ts
|
|
1029
1200
|
/**
|
|
@@ -1190,6 +1361,7 @@ var Agent = class extends Think {
|
|
|
1190
1361
|
const { tools, activeTools } = await this._prepareTools(ctx.tools, activeSkillName, true);
|
|
1191
1362
|
const baseSystem = await this.session.refreshSystemPrompt();
|
|
1192
1363
|
this._turn.baseSystem = [baseSystem, this._turn.skillsSystemBlock].filter(Boolean).join("\n\n");
|
|
1364
|
+
const turnTracing = await this.tracing.turn(this._turn.context ?? {}, this.messages);
|
|
1193
1365
|
return {
|
|
1194
1366
|
system: this._turn.baseSystem,
|
|
1195
1367
|
model: this.getModel(this._turn.context),
|
|
@@ -1206,7 +1378,7 @@ var Agent = class extends Think {
|
|
|
1206
1378
|
actorId: this.getTelemetryActorId(),
|
|
1207
1379
|
clientIp: this.clientIp,
|
|
1208
1380
|
forwardedFor: this.forwardedFor
|
|
1209
|
-
}),
|
|
1381
|
+
}, turnTracing),
|
|
1210
1382
|
metadata: {
|
|
1211
1383
|
agentName: this.getTelemetryAgentName(),
|
|
1212
1384
|
version: "v2",
|
|
@@ -1316,6 +1488,7 @@ var Agent = class extends Think {
|
|
|
1316
1488
|
}
|
|
1317
1489
|
}
|
|
1318
1490
|
workspaceBash = false;
|
|
1491
|
+
sendReasoning = false;
|
|
1319
1492
|
getSkillScriptRunner() {
|
|
1320
1493
|
if (!("LOADER" in this.env)) return null;
|
|
1321
1494
|
return skills.runner({ loader: this.env.LOADER });
|
|
@@ -1735,14 +1908,23 @@ var Agent = class extends Think {
|
|
|
1735
1908
|
actorId: this.getTelemetryActorId(),
|
|
1736
1909
|
attributes: this.getAnalyticsAttributes()
|
|
1737
1910
|
}),
|
|
1738
|
-
|
|
1911
|
+
getDataset: () => this.env.AGENTS_ANALYTICS,
|
|
1912
|
+
keepAlive: (callback) => this.keepAliveWhile(callback)
|
|
1913
|
+
});
|
|
1914
|
+
tracing = new Tracing({
|
|
1915
|
+
getIdentity: () => ({
|
|
1916
|
+
agentName: this.getTelemetryAgentName(),
|
|
1917
|
+
durableObjectName: this.name,
|
|
1918
|
+
actorId: this.getTelemetryActorId()
|
|
1919
|
+
}),
|
|
1920
|
+
getHandlers: () => this.getTracingHandlers(),
|
|
1739
1921
|
keepAlive: (callback) => this.keepAliveWhile(callback)
|
|
1740
1922
|
});
|
|
1741
1923
|
/**
|
|
1742
|
-
*
|
|
1743
|
-
*
|
|
1924
|
+
* Destinations for completed turns and message feedback, e.g. a tracing
|
|
1925
|
+
* service. They run in the background; a failing one never affects the chat.
|
|
1744
1926
|
*/
|
|
1745
|
-
|
|
1927
|
+
getTracingHandlers() {
|
|
1746
1928
|
return [];
|
|
1747
1929
|
}
|
|
1748
1930
|
/** Business dimensions added to every analytics event. */
|
|
@@ -2968,7 +3150,14 @@ var ChatAgent = class extends Agent {
|
|
|
2968
3150
|
async submitMessageFeedback(messageId, rating, comment) {
|
|
2969
3151
|
const result = submitMessageFeedback(this.sql.bind(this), messageId, rating, comment);
|
|
2970
3152
|
const parent = await this.getParentAgent();
|
|
2971
|
-
if (rating === 1 || rating === -1 || rating === 0)
|
|
3153
|
+
if (rating === 1 || rating === -1 || rating === 0) {
|
|
3154
|
+
this.analytics.feedbackSubmitted(messageId, rating);
|
|
3155
|
+
this.tracing.feedback(this.messages, {
|
|
3156
|
+
messageId,
|
|
3157
|
+
rating,
|
|
3158
|
+
comment
|
|
3159
|
+
});
|
|
3160
|
+
}
|
|
2972
3161
|
if (parent?.recordChatFeedback) try {
|
|
2973
3162
|
await parent.recordChatFeedback(this.name, messageId, rating);
|
|
2974
3163
|
} catch (error) {
|
|
@@ -3068,4 +3257,4 @@ function getCurrentToolContext() {
|
|
|
3068
3257
|
return getCurrentAgent().agent._lastBody;
|
|
3069
3258
|
}
|
|
3070
3259
|
//#endregion
|
|
3071
|
-
export { Agent, Assistant, ChatAgent, action,
|
|
3260
|
+
export { Agent, Assistant, ChatAgent, action, createJwtAuthConfig, exchangeToken, getCurrentToolContext, routeAgentRequest, skill, tool };
|
package/dist/providers.d.mts
CHANGED
|
@@ -18,6 +18,11 @@ interface AnthropicVertexProviderOptions {
|
|
|
18
18
|
/** Custom metadata included on every request. `agentId` is injected by the SDK. */
|
|
19
19
|
metadata?: AiGatewayMetadata;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Models are returned without per-model defaults. Set `effort` yourself via
|
|
23
|
+
* `providerOptions.anthropic` where it matters — Sonnet 5 otherwise runs at
|
|
24
|
+
* its default effort, which is slower and spends more tokens thinking.
|
|
25
|
+
*/
|
|
21
26
|
declare function createAnthropicProvider(options: AnthropicVertexProviderOptions): (modelId: AnthropicVertexModelId, metadata?: AiGatewayMetadata) => AnthropicMessagesLanguageModel;
|
|
22
27
|
//#endregion
|
|
23
28
|
//#region src/providers/gemini.d.ts
|
package/dist/providers.mjs
CHANGED
|
@@ -30,6 +30,11 @@ function createAiGatewayFetch(cloudflareApiToken, defaultMetadata, requestMetada
|
|
|
30
30
|
}
|
|
31
31
|
//#endregion
|
|
32
32
|
//#region src/providers/anthropic.ts
|
|
33
|
+
/**
|
|
34
|
+
* Models are returned without per-model defaults. Set `effort` yourself via
|
|
35
|
+
* `providerOptions.anthropic` where it matters — Sonnet 5 otherwise runs at
|
|
36
|
+
* its default effort, which is slower and spends more tokens thinking.
|
|
37
|
+
*/
|
|
33
38
|
function createAnthropicProvider(options) {
|
|
34
39
|
const { anthropicVersion = "vertex-2023-10-16", cloudflareAccountId, cloudflareAiGatewayId, cloudflareApiToken, googleCloudProjectId, location = "europe-west1", metadata: defaultMetadata } = options;
|
|
35
40
|
const anthropicBaseURL = `${`${`https://gateway.ai.cloudflare.com/v1/${cloudflareAccountId}/${cloudflareAiGatewayId}/`}google-vertex-ai/v1/projects/${googleCloudProjectId}/locations/${location}/publishers/`}anthropic/models/`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@economic/agents",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.15.1",
|
|
4
4
|
"description": "A starter for creating a TypeScript package.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"@ai-sdk/google": "^3.0.73",
|
|
27
27
|
"@cloudflare/ai-chat": "^0.11.0",
|
|
28
28
|
"@cloudflare/think": "^0.17.0",
|
|
29
|
+
"@opentelemetry/api": "^1.9.0",
|
|
29
30
|
"@opentelemetry/sdk-trace-base": "^2.8.0",
|
|
30
31
|
"agents": "^0.22.0",
|
|
31
32
|
"ai": "^6.0.197",
|