@economic/agents 3.1.0 → 3.2.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 CHANGED
@@ -675,6 +675,24 @@ export class SupportAgent extends Agent<RequestContext, UserContext> {
675
675
  - **Requests without the caller's token,** such as an OAuth callback or a signed webhook, are refused unless `acceptsWithoutToken(request)` returns `true` for them. Check them another way.
676
676
  - **Any authenticator works:** `getAuthenticator(env)` can return your own `Authenticator`, such as one for a different identity provider.
677
677
 
678
+ ### Reading without chatting
679
+
680
+ Return `false` from `canChat(userContext)` to let a user read their chats but not chat in them.
681
+
682
+ - **Refused:** messages, approval answers, a client tool's result that would start another turn, and `run`. Nothing they send is kept.
683
+ - **Still allowed:** reading the chat, rating messages, stopping a turn, and a client tool's result for the turn that's running. A running turn finishes.
684
+ - **The client** is told in the `connected` frame (`canChat: false`), which `useChat` returns as `canChat`.
685
+ - **When it's asked:** once per connection, when the user connects, so a change reaches them when they reconnect. An error counts as `false`. An override of `onConnect` must call `super.onConnect`.
686
+ - **The connection** is marked readonly, so it can't change the agent's state either.
687
+
688
+ ```ts
689
+ export class SupportChat extends ChatAgent<RequestContext, UserContext> {
690
+ canChat(userContext: UserContext | undefined) {
691
+ return userContext?.hasSupportAgent === true;
692
+ }
693
+ }
694
+ ```
695
+
678
696
  ## Memory
679
697
 
680
698
  Return `memory: true` from `getAgentConfig()` to give each user a private memory with the agent, kept across all their chats in their `UserStore` (`AGENTS_USERS`). It's a writable block of the system prompt, which the model updates itself with `set_context`.
@@ -792,6 +810,7 @@ Imported from `@economic/agents` unless noted.
792
810
  | `configureContext()` | `Agent` | Think's system prompt blocks. An override must include `...(await super.configureContext())`, or the agent's own prompt and rules are lost. |
793
811
  | `getTracingHandlers()` | `Agent` | Destinations for completed turns and message feedback (default: `[]`). |
794
812
  | `static getAuthenticator(env)` | `Agent` | Optional caller authentication, credentials and user context. |
813
+ | `canChat(userContext)` | `Agent` | Whether the user can send messages and answer approvals (default `true`). See [Reading without chatting](#reading-without-chatting). |
795
814
  | `submitMessageFeedback` / `getMessageFeedback` | `ChatAgent` | Callable message feedback. |
796
815
  | `getAgentConfig(ctx?)` | `Agent` | Settings for a turn (default `{}`): `maxSteps`, `temperature`, `maxOutputTokens`, `topP`, `toolChoice`, `maxRetries`, `timeout`, `sendReasoning`, `providerOptions`; `toolCallLimits`, how often each user may call each tool by name, per day (`dailyCalls`) and per minute (`burst`), which needs `AGENTS_USERS`; for a chat, `memory`, a private memory per user, and `deleteMessagesAfterMs`, after which its messages are deleted. |
797
816
  | `agent` | `Assistant` | Required. Your `ChatAgent` subclass. |
package/dist/index.d.mts CHANGED
@@ -12873,6 +12873,24 @@ export declare abstract class Agent<RequestContext extends Record<string, unknow
12873
12873
  */
12874
12874
  protected acceptsWithoutToken(_request: Request): boolean;
12875
12875
  onConnect(connection: Connection, ctx: ConnectionContext): Promise<void>;
12876
+ /**
12877
+ * Whether the user can send messages and answer approvals. When it's
12878
+ * `false` they can still read the chat. Asked for each connection in
12879
+ * `onConnect`, so an override of `onConnect` must call `super`. An error
12880
+ * counts as `false`. `true` by default.
12881
+ */
12882
+ canChat(_userContext: UserContext | undefined): boolean | Promise<boolean>;
12883
+ /** Answers still being asked, by connection, so messages sent meanwhile wait for them. */
12884
+ private _canChatAnswers;
12885
+ /**
12886
+ * Asks `canChat` for a connection. A no marks it readonly, which outlives
12887
+ * hibernation and the user's token.
12888
+ */
12889
+ private _askCanChat;
12890
+ /** Whether the connection can chat. Calls from the server, without one, can. */
12891
+ protected _canChat(connection?: Connection | undefined): Promise<boolean>;
12892
+ /** Think stores a message before any hook runs, so it's refused before Think sees it. */
12893
+ private _refuseChatMessages;
12876
12894
  protected _turn: CurrentTurnState<RequestContext, UserContext>;
12877
12895
  protected createToolContextForCurrentTurn(requestContext?: RequestContext): ToolContext<RequestContext, UserContext>;
12878
12896
  protected getToolContextForCurrentTurn(): ToolContext<RequestContext, UserContext> | undefined;
@@ -13090,7 +13108,11 @@ export declare abstract class Agent<RequestContext extends Record<string, unknow
13090
13108
  * as a server error.
13091
13109
  */
13092
13110
  protected authenticate(request: Request): Promise<void>;
13093
- private _logContext;
13111
+ protected _logContext(): {
13112
+ agentName: string;
13113
+ agentClass: string;
13114
+ durableObjectName: string;
13115
+ };
13094
13116
  /**
13095
13117
  * Self-registers this DO in the global `agent_registry`. Only top-level DOs
13096
13118
  * are registered — facets are enumerable via their parent's own index, so
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as aiSdk from "ai";
2
- import { Output, asSchema, convertToModelMessages, generateId, generateText, getToolName, hasToolCall, isToolUIPart, jsonSchema, pruneMessages, stepCountIs, tool as tool$1 } from "ai";
2
+ import { AISDKError, APICallError, Output, asSchema, convertToModelMessages, generateId, generateText, getToolName, hasToolCall, isToolUIPart, jsonSchema, pruneMessages, stepCountIs, tool as tool$1 } from "ai";
3
3
  import { isPrivateOrLocalHost } from "agents/webfetch";
4
4
  import { z } from "zod";
5
5
  import { truncateResult } from "@cloudflare/codemode";
@@ -28166,7 +28166,7 @@ const symbol$16 = Symbol.for(marker$16);
28166
28166
  * Custom error class for AI SDK related errors.
28167
28167
  * @extends Error
28168
28168
  */
28169
- var AISDKError = class AISDKError extends Error {
28169
+ var AISDKError$1 = class AISDKError extends Error {
28170
28170
  /**
28171
28171
  * Creates an AI SDK Error.
28172
28172
  *
@@ -28197,7 +28197,7 @@ var AISDKError = class AISDKError extends Error {
28197
28197
  const name$14 = "AI_APICallError";
28198
28198
  const marker$14 = `vercel.ai.error.${name$14}`;
28199
28199
  const symbol$14 = Symbol.for(marker$14);
28200
- var APICallError = class extends AISDKError {
28200
+ var APICallError$1 = class extends AISDKError$1 {
28201
28201
  constructor({ message, url, requestBodyValues, statusCode, responseHeaders, responseBody, cause, isRetryable = statusCode != null && (statusCode === 408 || statusCode === 409 || statusCode === 429 || statusCode >= 500), data }) {
28202
28202
  super({
28203
28203
  name: name$14,
@@ -28214,13 +28214,13 @@ var APICallError = class extends AISDKError {
28214
28214
  this.data = data;
28215
28215
  }
28216
28216
  static isInstance(error) {
28217
- return AISDKError.hasMarker(error, marker$14);
28217
+ return AISDKError$1.hasMarker(error, marker$14);
28218
28218
  }
28219
28219
  };
28220
28220
  const name$13 = "AI_EmptyResponseBodyError";
28221
28221
  const marker$13 = `vercel.ai.error.${name$13}`;
28222
28222
  const symbol$13 = Symbol.for(marker$13);
28223
- var EmptyResponseBodyError = class extends AISDKError {
28223
+ var EmptyResponseBodyError = class extends AISDKError$1 {
28224
28224
  constructor({ message = "Empty response body" } = {}) {
28225
28225
  super({
28226
28226
  name: name$13,
@@ -28229,7 +28229,7 @@ var EmptyResponseBodyError = class extends AISDKError {
28229
28229
  this[symbol$13] = true;
28230
28230
  }
28231
28231
  static isInstance(error) {
28232
- return AISDKError.hasMarker(error, marker$13);
28232
+ return AISDKError$1.hasMarker(error, marker$13);
28233
28233
  }
28234
28234
  };
28235
28235
  function getErrorMessage(error) {
@@ -28244,7 +28244,7 @@ const symbol$11 = Symbol.for(marker$11);
28244
28244
  /**
28245
28245
  * A function argument is invalid.
28246
28246
  */
28247
- var InvalidArgumentError = class extends AISDKError {
28247
+ var InvalidArgumentError = class extends AISDKError$1 {
28248
28248
  constructor({ message, cause, argument }) {
28249
28249
  super({
28250
28250
  name: name$11,
@@ -28255,7 +28255,7 @@ var InvalidArgumentError = class extends AISDKError {
28255
28255
  this.argument = argument;
28256
28256
  }
28257
28257
  static isInstance(error) {
28258
- return AISDKError.hasMarker(error, marker$11);
28258
+ return AISDKError$1.hasMarker(error, marker$11);
28259
28259
  }
28260
28260
  };
28261
28261
  const name$10 = "AI_InvalidPromptError";
@@ -28265,7 +28265,7 @@ const symbol$10 = Symbol.for(marker$10);
28265
28265
  * A prompt is invalid. This error should be thrown by providers when they cannot
28266
28266
  * process a prompt.
28267
28267
  */
28268
- var InvalidPromptError = class extends AISDKError {
28268
+ var InvalidPromptError = class extends AISDKError$1 {
28269
28269
  constructor({ prompt, message, cause }) {
28270
28270
  super({
28271
28271
  name: name$10,
@@ -28276,7 +28276,7 @@ var InvalidPromptError = class extends AISDKError {
28276
28276
  this.prompt = prompt;
28277
28277
  }
28278
28278
  static isInstance(error) {
28279
- return AISDKError.hasMarker(error, marker$10);
28279
+ return AISDKError$1.hasMarker(error, marker$10);
28280
28280
  }
28281
28281
  };
28282
28282
  const name$9 = "AI_InvalidResponseDataError";
@@ -28286,7 +28286,7 @@ const symbol$9 = Symbol.for(marker$9);
28286
28286
  * Server returned a response with invalid data content.
28287
28287
  * This should be thrown by providers when they cannot parse the response from the API.
28288
28288
  */
28289
- var InvalidResponseDataError = class extends AISDKError {
28289
+ var InvalidResponseDataError = class extends AISDKError$1 {
28290
28290
  constructor({ data, message = `Invalid response data: ${JSON.stringify(data)}.` }) {
28291
28291
  super({
28292
28292
  name: name$9,
@@ -28296,13 +28296,13 @@ var InvalidResponseDataError = class extends AISDKError {
28296
28296
  this.data = data;
28297
28297
  }
28298
28298
  static isInstance(error) {
28299
- return AISDKError.hasMarker(error, marker$9);
28299
+ return AISDKError$1.hasMarker(error, marker$9);
28300
28300
  }
28301
28301
  };
28302
28302
  const name$8 = "AI_JSONParseError";
28303
28303
  const marker$8 = `vercel.ai.error.${name$8}`;
28304
28304
  const symbol$8 = Symbol.for(marker$8);
28305
- var JSONParseError = class extends AISDKError {
28305
+ var JSONParseError = class extends AISDKError$1 {
28306
28306
  constructor({ text, cause }) {
28307
28307
  super({
28308
28308
  name: name$8,
@@ -28313,13 +28313,13 @@ var JSONParseError = class extends AISDKError {
28313
28313
  this.text = text;
28314
28314
  }
28315
28315
  static isInstance(error) {
28316
- return AISDKError.hasMarker(error, marker$8);
28316
+ return AISDKError$1.hasMarker(error, marker$8);
28317
28317
  }
28318
28318
  };
28319
28319
  const name$7 = "AI_LoadAPIKeyError";
28320
28320
  const marker$7 = `vercel.ai.error.${name$7}`;
28321
28321
  const symbol$7 = Symbol.for(marker$7);
28322
- var LoadAPIKeyError = class extends AISDKError {
28322
+ var LoadAPIKeyError = class extends AISDKError$1 {
28323
28323
  constructor({ message }) {
28324
28324
  super({
28325
28325
  name: name$7,
@@ -28328,7 +28328,7 @@ var LoadAPIKeyError = class extends AISDKError {
28328
28328
  this[symbol$7] = true;
28329
28329
  }
28330
28330
  static isInstance(error) {
28331
- return AISDKError.hasMarker(error, marker$7);
28331
+ return AISDKError$1.hasMarker(error, marker$7);
28332
28332
  }
28333
28333
  };
28334
28334
  const name$3 = "AI_NoSuchProviderReferenceError";
@@ -28338,7 +28338,7 @@ const symbol$3 = Symbol.for(marker$3);
28338
28338
  * Thrown when a provider reference cannot be resolved because the specified
28339
28339
  * provider is not found in the provider reference mapping.
28340
28340
  */
28341
- var NoSuchProviderReferenceError = class extends AISDKError {
28341
+ var NoSuchProviderReferenceError = class extends AISDKError$1 {
28342
28342
  constructor({ provider, reference, message = `No provider reference found for provider '${provider}'. Available providers: ${Object.keys(reference).join(", ")}` }) {
28343
28343
  super({
28344
28344
  name: name$3,
@@ -28349,13 +28349,13 @@ var NoSuchProviderReferenceError = class extends AISDKError {
28349
28349
  this.reference = reference;
28350
28350
  }
28351
28351
  static isInstance(error) {
28352
- return AISDKError.hasMarker(error, marker$3);
28352
+ return AISDKError$1.hasMarker(error, marker$3);
28353
28353
  }
28354
28354
  };
28355
28355
  const name$2 = "AI_TooManyEmbeddingValuesForCallError";
28356
28356
  const marker$2$1 = `vercel.ai.error.${name$2}`;
28357
28357
  const symbol$2 = Symbol.for(marker$2$1);
28358
- var TooManyEmbeddingValuesForCallError = class extends AISDKError {
28358
+ var TooManyEmbeddingValuesForCallError = class extends AISDKError$1 {
28359
28359
  constructor(options) {
28360
28360
  super({
28361
28361
  name: name$2,
@@ -28368,13 +28368,13 @@ var TooManyEmbeddingValuesForCallError = class extends AISDKError {
28368
28368
  this.values = options.values;
28369
28369
  }
28370
28370
  static isInstance(error) {
28371
- return AISDKError.hasMarker(error, marker$2$1);
28371
+ return AISDKError$1.hasMarker(error, marker$2$1);
28372
28372
  }
28373
28373
  };
28374
28374
  const name$1$1 = "AI_TypeValidationError";
28375
28375
  const marker$1$1 = `vercel.ai.error.${name$1$1}`;
28376
28376
  const symbol$1$1 = Symbol.for(marker$1$1);
28377
- var TypeValidationError = class TypeValidationError extends AISDKError {
28377
+ var TypeValidationError = class TypeValidationError extends AISDKError$1 {
28378
28378
  constructor({ value, cause, context }) {
28379
28379
  let contextPrefix = "Type validation failed";
28380
28380
  if (context?.field) contextPrefix += ` for ${context.field}`;
@@ -28396,7 +28396,7 @@ var TypeValidationError = class TypeValidationError extends AISDKError {
28396
28396
  this.context = context;
28397
28397
  }
28398
28398
  static isInstance(error) {
28399
- return AISDKError.hasMarker(error, marker$1$1);
28399
+ return AISDKError$1.hasMarker(error, marker$1$1);
28400
28400
  }
28401
28401
  /**
28402
28402
  * Wraps an error into a TypeValidationError.
@@ -28421,7 +28421,7 @@ var TypeValidationError = class TypeValidationError extends AISDKError {
28421
28421
  const name$16 = "AI_UnsupportedFunctionalityError";
28422
28422
  const marker$17 = `vercel.ai.error.${name$16}`;
28423
28423
  const symbol$17 = Symbol.for(marker$17);
28424
- var UnsupportedFunctionalityError = class extends AISDKError {
28424
+ var UnsupportedFunctionalityError = class extends AISDKError$1 {
28425
28425
  constructor({ functionality, message = `'${functionality}' functionality not supported.` }) {
28426
28426
  super({
28427
28427
  name: name$16,
@@ -28431,7 +28431,7 @@ var UnsupportedFunctionalityError = class extends AISDKError {
28431
28431
  this.functionality = functionality;
28432
28432
  }
28433
28433
  static isInstance(error) {
28434
- return AISDKError.hasMarker(error, marker$17);
28434
+ return AISDKError$1.hasMarker(error, marker$17);
28435
28435
  }
28436
28436
  };
28437
28437
  function isJSONValue(value) {
@@ -28457,11 +28457,11 @@ function isJSONObject(value) {
28457
28457
  * prior behavior).
28458
28458
  */
28459
28459
  function normalizeBindingError(error, context) {
28460
- if (APICallError.isInstance(error) || isAbortError$1(error)) return error;
28460
+ if (APICallError$1.isInstance(error) || isAbortError$1(error)) return error;
28461
28461
  const code = parseWorkersAIErrorCode(error);
28462
28462
  const statusCode = code != null ? WORKERS_AI_ERROR_CODE_TO_STATUS[code] : void 0;
28463
28463
  const message = messageOf(error);
28464
- return new APICallError({
28464
+ return new APICallError$1({
28465
28465
  message,
28466
28466
  url: `workers-ai:binding/run/${context.model}`,
28467
28467
  requestBodyValues: context.requestBodyValues,
@@ -28479,7 +28479,7 @@ function normalizeBindingError(error, context) {
28479
28479
  * `"Workers AI API error (<status> <statusText>): <body>"` shape.
28480
28480
  */
28481
28481
  function apiCallErrorFromResponse(response, errorBody, context) {
28482
- return new APICallError({
28482
+ return new APICallError$1({
28483
28483
  message: `Workers AI API error (${response.status} ${response.statusText}): ${errorBody}`,
28484
28484
  url: context.url,
28485
28485
  requestBodyValues: context.requestBodyValues,
@@ -31347,7 +31347,7 @@ function handleFetchError({ error, url, requestBodyValues }) {
31347
31347
  if (isAbortError(error)) return error;
31348
31348
  if (error instanceof TypeError && FETCH_FAILED_ERROR_MESSAGES.includes(error.message.toLowerCase())) {
31349
31349
  const cause = error.cause;
31350
- if (cause != null) return new APICallError({
31350
+ if (cause != null) return new APICallError$1({
31351
31351
  message: `Cannot connect to API: ${cause.message}`,
31352
31352
  cause,
31353
31353
  url,
@@ -31357,7 +31357,7 @@ function handleFetchError({ error, url, requestBodyValues }) {
31357
31357
  }
31358
31358
  const networkError = findNetworkError(error);
31359
31359
  if (networkError != null) {
31360
- if (APICallError.isInstance(error)) return new APICallError({
31360
+ if (APICallError$1.isInstance(error)) return new APICallError$1({
31361
31361
  message: error.message,
31362
31362
  cause: error.cause,
31363
31363
  url: error.url,
@@ -31368,7 +31368,7 @@ function handleFetchError({ error, url, requestBodyValues }) {
31368
31368
  data: error.data,
31369
31369
  isRetryable: true
31370
31370
  });
31371
- return new APICallError({
31371
+ return new APICallError$1({
31372
31372
  message: `Cannot connect to API: ${error instanceof Error ? error.message : networkError.message}`,
31373
31373
  cause: error,
31374
31374
  url,
@@ -31437,8 +31437,8 @@ const deleteFromApi = async ({ url, headers = {}, failedResponseHandler, success
31437
31437
  requestBodyValues: {}
31438
31438
  });
31439
31439
  } catch (error) {
31440
- if (isAbortError(error) || APICallError.isInstance(error)) throw error;
31441
- throw new APICallError({
31440
+ if (isAbortError(error) || APICallError$1.isInstance(error)) throw error;
31441
+ throw new APICallError$1({
31442
31442
  message: "Failed to process error response",
31443
31443
  cause: error,
31444
31444
  statusCode: response.status,
@@ -31457,9 +31457,9 @@ const deleteFromApi = async ({ url, headers = {}, failedResponseHandler, success
31457
31457
  });
31458
31458
  } catch (error) {
31459
31459
  if (error instanceof Error) {
31460
- if (isAbortError(error) || APICallError.isInstance(error)) throw error;
31460
+ if (isAbortError(error) || APICallError$1.isInstance(error)) throw error;
31461
31461
  }
31462
- throw new APICallError({
31462
+ throw new APICallError$1({
31463
31463
  message: "Failed to process successful response",
31464
31464
  cause: error,
31465
31465
  statusCode: response.status,
@@ -31868,7 +31868,7 @@ async function cancelResponseBody(response) {
31868
31868
  const name$1 = "AI_DownloadError";
31869
31869
  const marker$1 = `vercel.ai.error.${name$1}`;
31870
31870
  const symbol$1 = Symbol.for(marker$1);
31871
- var DownloadError = class extends AISDKError {
31871
+ var DownloadError = class extends AISDKError$1 {
31872
31872
  constructor({ url, statusCode, statusText, cause, message = cause == null ? `Failed to download ${url}: ${statusCode} ${statusText}` : `Failed to download ${url}: ${cause}` }) {
31873
31873
  super({
31874
31874
  name: name$1,
@@ -31881,7 +31881,7 @@ var DownloadError = class extends AISDKError {
31881
31881
  this.statusText = statusText;
31882
31882
  }
31883
31883
  static isInstance(error) {
31884
- return AISDKError.hasMarker(error, marker$1);
31884
+ return AISDKError$1.hasMarker(error, marker$1);
31885
31885
  }
31886
31886
  };
31887
31887
  /**
@@ -32499,8 +32499,8 @@ const getFromApi = async ({ url, headers = {}, successfulResponseHandler, failed
32499
32499
  requestBodyValues: {}
32500
32500
  });
32501
32501
  } catch (error) {
32502
- if (isAbortError(error) || APICallError.isInstance(error)) throw error;
32503
- throw new APICallError({
32502
+ if (isAbortError(error) || APICallError$1.isInstance(error)) throw error;
32503
+ throw new APICallError$1({
32504
32504
  message: "Failed to process error response",
32505
32505
  cause: error,
32506
32506
  statusCode: response.status,
@@ -32519,9 +32519,9 @@ const getFromApi = async ({ url, headers = {}, successfulResponseHandler, failed
32519
32519
  });
32520
32520
  } catch (error) {
32521
32521
  if (error instanceof Error) {
32522
- if (isAbortError(error) || APICallError.isInstance(error)) throw error;
32522
+ if (isAbortError(error) || APICallError$1.isInstance(error)) throw error;
32523
32523
  }
32524
- throw new APICallError({
32524
+ throw new APICallError$1({
32525
32525
  message: "Failed to process successful response",
32526
32526
  cause: error,
32527
32527
  statusCode: response.status,
@@ -33867,8 +33867,8 @@ const postMultipartStreamToApi = async ({ url, headers = {}, parts, failedRespon
33867
33867
  requestBodyValues
33868
33868
  });
33869
33869
  } catch (error) {
33870
- if (isAbortError(error) || APICallError.isInstance(error)) throw error;
33871
- throw new APICallError({
33870
+ if (isAbortError(error) || APICallError$1.isInstance(error)) throw error;
33871
+ throw new APICallError$1({
33872
33872
  message: "Failed to process error response",
33873
33873
  cause: error,
33874
33874
  statusCode: response.status,
@@ -33887,9 +33887,9 @@ const postMultipartStreamToApi = async ({ url, headers = {}, parts, failedRespon
33887
33887
  });
33888
33888
  } catch (error) {
33889
33889
  if (error instanceof Error) {
33890
- if (isAbortError(error) || APICallError.isInstance(error)) throw error;
33890
+ if (isAbortError(error) || APICallError$1.isInstance(error)) throw error;
33891
33891
  }
33892
- throw new APICallError({
33892
+ throw new APICallError$1({
33893
33893
  message: "Failed to process successful response",
33894
33894
  cause: error,
33895
33895
  statusCode: response.status,
@@ -33953,8 +33953,8 @@ const postToApi = async ({ url, headers = {}, body, successfulResponseHandler, f
33953
33953
  requestBodyValues: body.values
33954
33954
  });
33955
33955
  } catch (error) {
33956
- if (isAbortError(error) || APICallError.isInstance(error)) throw error;
33957
- throw new APICallError({
33956
+ if (isAbortError(error) || APICallError$1.isInstance(error)) throw error;
33957
+ throw new APICallError$1({
33958
33958
  message: "Failed to process error response",
33959
33959
  cause: error,
33960
33960
  statusCode: response.status,
@@ -33973,9 +33973,9 @@ const postToApi = async ({ url, headers = {}, body, successfulResponseHandler, f
33973
33973
  });
33974
33974
  } catch (error) {
33975
33975
  if (error instanceof Error) {
33976
- if (isAbortError(error) || APICallError.isInstance(error)) throw error;
33976
+ if (isAbortError(error) || APICallError$1.isInstance(error)) throw error;
33977
33977
  }
33978
- throw new APICallError({
33978
+ throw new APICallError$1({
33979
33979
  message: "Failed to process successful response",
33980
33980
  cause: error,
33981
33981
  statusCode: response.status,
@@ -34104,7 +34104,7 @@ function wrapResponseBodyStream({ stream, url, requestBodyValues, statusCode, re
34104
34104
  return;
34105
34105
  }
34106
34106
  controller.error(handleFetchError({
34107
- error: new APICallError({
34107
+ error: new APICallError$1({
34108
34108
  message: "Failed to process successful response",
34109
34109
  cause: error,
34110
34110
  statusCode,
@@ -34140,7 +34140,7 @@ const createJsonErrorResponseHandler = ({ errorSchema, errorToMessage, isRetryab
34140
34140
  const responseHeaders = extractResponseHeaders(response);
34141
34141
  if (responseBody.trim() === "") return {
34142
34142
  responseHeaders,
34143
- value: new APICallError({
34143
+ value: new APICallError$1({
34144
34144
  message: response.statusText,
34145
34145
  url,
34146
34146
  requestBodyValues,
@@ -34157,7 +34157,7 @@ const createJsonErrorResponseHandler = ({ errorSchema, errorToMessage, isRetryab
34157
34157
  });
34158
34158
  return {
34159
34159
  responseHeaders,
34160
- value: new APICallError({
34160
+ value: new APICallError$1({
34161
34161
  message: errorToMessage(parsedError),
34162
34162
  url,
34163
34163
  requestBodyValues,
@@ -34171,7 +34171,7 @@ const createJsonErrorResponseHandler = ({ errorSchema, errorToMessage, isRetryab
34171
34171
  } catch {
34172
34172
  return {
34173
34173
  responseHeaders,
34174
- value: new APICallError({
34174
+ value: new APICallError$1({
34175
34175
  message: response.statusText,
34176
34176
  url,
34177
34177
  requestBodyValues,
@@ -34210,7 +34210,7 @@ const createJsonResponseHandler = (responseSchema) => async ({ response, url, re
34210
34210
  schema: responseSchema
34211
34211
  });
34212
34212
  const responseHeaders = extractResponseHeaders(response);
34213
- if (!parsedResult.success) throw new APICallError({
34213
+ if (!parsedResult.success) throw new APICallError$1({
34214
34214
  message: "Invalid JSON response",
34215
34215
  cause: parsedResult.error,
34216
34216
  statusCode: response.status,
@@ -34292,7 +34292,7 @@ async function* parseJsonLines({ stream, schema, url, maxLineBytes }) {
34292
34292
  }
34293
34293
  const createBinaryResponseHandler = () => async ({ response, url, requestBodyValues }) => {
34294
34294
  const responseHeaders = extractResponseHeaders(response);
34295
- if (!response.body) throw new APICallError({
34295
+ if (!response.body) throw new APICallError$1({
34296
34296
  message: "Response body is empty",
34297
34297
  url,
34298
34298
  requestBodyValues,
@@ -34307,7 +34307,7 @@ const createBinaryResponseHandler = () => async ({ response, url, requestBodyVal
34307
34307
  value: new Uint8Array(buffer)
34308
34308
  };
34309
34309
  } catch (error) {
34310
- throw new APICallError({
34310
+ throw new APICallError$1({
34311
34311
  message: "Failed to read response as array buffer",
34312
34312
  url,
34313
34313
  requestBodyValues,
@@ -34356,7 +34356,7 @@ function isJSONSerializable(value) {
34356
34356
  const name = "AI_SerializationError";
34357
34357
  const marker = `vercel.ai.error.${name}`;
34358
34358
  const symbol = Symbol.for(marker);
34359
- var SerializationError = class extends AISDKError {
34359
+ var SerializationError = class extends AISDKError$1 {
34360
34360
  constructor({ message = "Failed to serialize value.", cause } = {}) {
34361
34361
  super({
34362
34362
  name,
@@ -34366,7 +34366,7 @@ var SerializationError = class extends AISDKError {
34366
34366
  this[symbol] = true;
34367
34367
  }
34368
34368
  static isInstance(error) {
34369
- return AISDKError.hasMarker(error, marker);
34369
+ return AISDKError$1.hasMarker(error, marker);
34370
34370
  }
34371
34371
  };
34372
34372
  /**
@@ -35098,7 +35098,7 @@ async function raceWithTimeout(promise, timeoutMs) {
35098
35098
  }
35099
35099
  function createOpenAIStreamError({ frame, url, requestBodyValues, responseHeaders }) {
35100
35100
  const streamError = createOpenAIProviderStreamError(frame);
35101
- return new APICallError({
35101
+ return new APICallError$1({
35102
35102
  message: streamError?.message ?? "OpenAI stream failed before any output was generated",
35103
35103
  url,
35104
35104
  requestBodyValues,
@@ -41160,7 +41160,7 @@ var OpenAIResponsesLanguageModel = class OpenAIResponsesLanguageModel {
41160
41160
  abortSignal: options.abortSignal,
41161
41161
  fetch: this.config.fetch
41162
41162
  });
41163
- if (response.error) throw new APICallError({
41163
+ if (response.error) throw new APICallError$1({
41164
41164
  message: response.error.message,
41165
41165
  url,
41166
41166
  requestBodyValues: body,
@@ -41171,7 +41171,7 @@ var OpenAIResponsesLanguageModel = class OpenAIResponsesLanguageModel {
41171
41171
  });
41172
41172
  if (response.output == null) {
41173
41173
  const detail = response.incomplete_details?.reason;
41174
- throw new APICallError({
41174
+ throw new APICallError$1({
41175
41175
  message: detail ? `Responses API returned no output (${detail})` : "Responses API returned no output",
41176
41176
  url,
41177
41177
  requestBodyValues: body,
@@ -42566,7 +42566,7 @@ function isOpenAIChatCompletionChunk(value) {
42566
42566
  return chunk != null && Array.isArray(chunk.choices) && typeof chunk.type !== "string";
42567
42567
  }
42568
42568
  function createOpenAIResponsesChatCompletionsMismatchError({ value, cause, url, requestBodyValues, responseHeaders }) {
42569
- return new APICallError({
42569
+ return new APICallError$1({
42570
42570
  message: "Received a Chat Completions stream while using the OpenAI Responses API. The default OpenAI provider model uses the Responses API. If your custom baseURL targets a Chat Completions-compatible endpoint, use openai.chat('model-id') or createOpenAI(...).chat('model-id') instead. You can also use @ai-sdk/openai-compatible for OpenAI-compatible providers.",
42571
42571
  url,
42572
42572
  requestBodyValues,
@@ -69941,18 +69941,38 @@ function writeOperationalLog(record, logger = console) {
69941
69941
  else logger.log(message);
69942
69942
  } catch {}
69943
69943
  }
69944
+ /**
69945
+ * What's safe to log of an error. An AI SDK error's message can quote what
69946
+ * the model was sent or returned, such as a value that failed validation, so
69947
+ * only its name is kept, and an API call's message, which is the provider's.
69948
+ */
69949
+ function describeError(error) {
69950
+ if (!AISDKError.isInstance(error)) return {
69951
+ name: error.name,
69952
+ message: error.message,
69953
+ stack: error.stack
69954
+ };
69955
+ return APICallError.isInstance(error) ? {
69956
+ name: error.name,
69957
+ message: error.message
69958
+ } : { name: error.name };
69959
+ }
69944
69960
  function errorAttributes(error) {
69945
69961
  if (!(error instanceof Error)) return { error: String(error) };
69962
+ const { name, message, stack } = describeError(error);
69946
69963
  const causes = [];
69947
69964
  let cause = error.cause;
69948
69965
  while (cause !== void 0 && causes.length < 5) {
69949
- causes.push(cause instanceof Error ? `${cause.name}: ${cause.message}` : String(cause));
69966
+ if (cause instanceof Error) {
69967
+ const described = describeError(cause);
69968
+ causes.push(described.message ? `${described.name}: ${described.message}` : described.name);
69969
+ } else causes.push(String(cause));
69950
69970
  cause = cause instanceof Error ? cause.cause : void 0;
69951
69971
  }
69952
69972
  return {
69953
- error: error.message,
69954
- errorType: error.name,
69955
- stack: error.stack,
69973
+ error: message,
69974
+ errorType: name,
69975
+ stack,
69956
69976
  ...causes.length ? { causes } : {}
69957
69977
  };
69958
69978
  }
@@ -71011,11 +71031,12 @@ var ToolApprovals = class {
71011
71031
  }
71012
71032
  };
71013
71033
  // src/server/util/agent.ts
71014
- function sendConnectionStatus(connection, status, reason) {
71034
+ function sendConnectionStatus(connection, status, reason, details) {
71015
71035
  connection.send(JSON.stringify({
71016
71036
  type: "connection_status",
71017
71037
  status,
71018
- reason
71038
+ reason,
71039
+ ...details
71019
71040
  }));
71020
71041
  }
71021
71042
  function sendAgentType(connection, agentType) {
@@ -71204,6 +71225,7 @@ const THINK_SKILLS_CONTEXT_LABEL = "think_skills";
71204
71225
  const SESSION_CONTEXT_SEPARATOR = "═".repeat(46);
71205
71226
  const TOOL_CONTINUATION_PREFIX = "economic:tool-continuation:";
71206
71227
  const TOOL_CONTINUATION_REQUEST_PREFIX = "economic:tool-continuation-request:";
71228
+ const CHAT_REFUSED = "You can read this chat, but you can't chat in it.";
71207
71229
  /** Whether a tool call's output is the paused or running output of an execution. */
71208
71230
  function showsExecution(part, executionId) {
71209
71231
  return "output" in part && part.output?.executionId === executionId;
@@ -71277,6 +71299,7 @@ var Agent = class Agent extends Think {
71277
71299
  this._tracingHandlers = this.getTracingHandlers();
71278
71300
  if (this.context.removeBlock(THINK_SKILLS_CONTEXT_LABEL)) await this.context.refreshSystemPrompt();
71279
71301
  this._unfinishedToolCalls = await this._toolTaskSettlements.unfinishedToolCalls();
71302
+ this._refuseChatMessages();
71280
71303
  this.registerInstance();
71281
71304
  }
71282
71305
  /**
@@ -71336,7 +71359,57 @@ var Agent = class Agent extends Think {
71336
71359
  connection.close(closeCode, message);
71337
71360
  return;
71338
71361
  }
71339
- sendConnectionStatus(connection, "connected");
71362
+ sendConnectionStatus(connection, "connected", void 0, { canChat: await this._askCanChat(connection) });
71363
+ }
71364
+ /**
71365
+ * Whether the user can send messages and answer approvals. When it's
71366
+ * `false` they can still read the chat. Asked for each connection in
71367
+ * `onConnect`, so an override of `onConnect` must call `super`. An error
71368
+ * counts as `false`. `true` by default.
71369
+ */
71370
+ canChat(_userContext) {
71371
+ return true;
71372
+ }
71373
+ /** Answers still being asked, by connection, so messages sent meanwhile wait for them. */
71374
+ _canChatAnswers = /* @__PURE__ */ new Map();
71375
+ /**
71376
+ * Asks `canChat` for a connection. A no marks it readonly, which outlives
71377
+ * hibernation and the user's token.
71378
+ */
71379
+ async _askCanChat(connection) {
71380
+ const answer = Promise.resolve().then(() => this.canChat(this.identity.userContext())).catch((error) => {
71381
+ logOperationalError("agent.can-chat.failed", error, this._logContext());
71382
+ return false;
71383
+ });
71384
+ this._canChatAnswers.set(connection.id, answer);
71385
+ const canChat = await answer;
71386
+ if (!canChat) this.setConnectionReadonly(connection, true);
71387
+ this._canChatAnswers.delete(connection.id);
71388
+ return canChat;
71389
+ }
71390
+ /** Whether the connection can chat. Calls from the server, without one, can. */
71391
+ async _canChat(connection = getCurrentAgent$1().connection) {
71392
+ if (!connection) return true;
71393
+ return await this._canChatAnswers.get(connection.id) ?? !this.isConnectionReadonly(connection);
71394
+ }
71395
+ /** Think stores a message before any hook runs, so it's refused before Think sees it. */
71396
+ _refuseChatMessages() {
71397
+ const onMessage = this.onMessage.bind(this);
71398
+ this.onMessage = async (connection, message) => {
71399
+ const event = typeof message === "string" ? parseProtocolMessage(message) : null;
71400
+ if (!((event?.type === "chat-request" || event?.type === "tool-approval" || event?.type === "tool-result" && event.autoContinue === true) && !this._cf_connectionTargetsSubAgent(connection) && !await this._canChat(connection))) return onMessage(connection, message);
71401
+ connection.send(JSON.stringify({
71402
+ type: CHAT_MESSAGE_TYPES.CHAT_MESSAGES,
71403
+ messages: this.messages
71404
+ }));
71405
+ if (event.type === "chat-request") connection.send(JSON.stringify({
71406
+ type: CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE,
71407
+ id: event.id,
71408
+ body: CHAT_REFUSED,
71409
+ done: true,
71410
+ error: true
71411
+ }));
71412
+ };
71340
71413
  }
71341
71414
  _turn = {
71342
71415
  inputTools: {},
@@ -71518,11 +71591,13 @@ var Agent = class Agent extends Think {
71518
71591
  }
71519
71592
  /** `all` also approves the tool call's later approvals, so it doesn't stop again. */
71520
71593
  async approveExecution(executionId, options) {
71594
+ if (!await this._canChat()) throw new Error(CHAT_REFUSED);
71521
71595
  const answer = await this._toolApprovals.answer(executionId, true, { all: options?.all });
71522
71596
  if (answer) return answer;
71523
71597
  return super.approveExecution(executionId);
71524
71598
  }
71525
71599
  async rejectExecution(executionId, reason, options) {
71600
+ if (!await this._canChat()) throw new Error(CHAT_REFUSED);
71526
71601
  const answer = await this._toolApprovals.answer(executionId, false, { autoContinue: options?.autoContinue });
71527
71602
  if (answer) return answer;
71528
71603
  return super.rejectExecution(executionId, reason, options);
@@ -72072,7 +72147,10 @@ var Agent = class Agent extends Think {
72072
72147
  for (const source of sources) try {
72073
72148
  for (const descriptor of await source.list()) if (!byName.has(descriptor.name)) byName.set(descriptor.name, source);
72074
72149
  } catch (error) {
72075
- console.warn(`[Agent] Failed to resolve skill source "${source.id}"`, error);
72150
+ logOperationalError("agent.skills.source.failed", error, {
72151
+ ...this._logContext(),
72152
+ attributes: { skillSource: source.id }
72153
+ });
72076
72154
  break;
72077
72155
  }
72078
72156
  return byName;
@@ -72352,7 +72430,7 @@ function ensureChatsTableExists(sql) {
72352
72430
  ensureChatsTitleSetByUserColumnExists(sql);
72353
72431
  ensureChatMessageFeedbackTableExists(sql);
72354
72432
  } catch (error) {
72355
- console.error("[Agent] Failed to create chats table", error);
72433
+ logOperationalError("agent.chats.table.failed", error);
72356
72434
  }
72357
72435
  }
72358
72436
  function ensureChatsFeedbackColumnsExist(sql) {
@@ -72446,8 +72524,12 @@ function chatSummaryInput(messages) {
72446
72524
  recentMessages: messages.slice(-20)
72447
72525
  };
72448
72526
  }
72449
- async function summariseChatWithAI(sql, durableObjectName, { completedTurns, recentMessages }, model) {
72527
+ async function summariseChatWithAI(sql, durableObjectName, { completedTurns, recentMessages }, model, logContext = {}) {
72450
72528
  const chat = getChat(sql, durableObjectName);
72529
+ const failureContext = {
72530
+ ...logContext,
72531
+ attributes: { chatName: durableObjectName }
72532
+ };
72451
72533
  if (!(!chat || !chat.title || isChatSummaryDue(completedTurns))) return;
72452
72534
  let systemPrompt = `
72453
72535
  You are a helpful assistant that summarises chats.
@@ -72480,7 +72562,7 @@ async function summariseChatWithAI(sql, durableObjectName, { completedTurns, rec
72480
72562
  }) })
72481
72563
  });
72482
72564
  if (!title || !summary) {
72483
- console.error("[Assistant] Failed to generate chat title and summary", { durableObjectName });
72565
+ logOperationalError("assistant.chat.summary.failed", /* @__PURE__ */ new Error("The model gave no title or summary."), failureContext);
72484
72566
  return;
72485
72567
  }
72486
72568
  sql`UPDATE chats
@@ -72490,10 +72572,7 @@ async function summariseChatWithAI(sql, durableObjectName, { completedTurns, rec
72490
72572
  WHERE durable_object_name = ${durableObjectName}`;
72491
72573
  console.info("[Assistant] Generated chat summary", { durableObjectName });
72492
72574
  } catch (error) {
72493
- console.error("[Assistant] Failed to generate chat title and summary", {
72494
- durableObjectName,
72495
- error
72496
- });
72575
+ logOperationalError("assistant.chat.summary.failed", error, failureContext);
72497
72576
  }
72498
72577
  }
72499
72578
  const DELETE_CHAT_CALLBACK = "deleteChatCallback";
@@ -72778,7 +72857,7 @@ var Assistant = class extends Agent$1 {
72778
72857
  async recordChatTurn(durableObjectName, summaryInput) {
72779
72858
  touchChat(this.sql.bind(this), durableObjectName);
72780
72859
  this.keepAliveWhile(async () => {
72781
- await Promise.all([summaryInput && summariseChatWithAI(this.sql.bind(this), durableObjectName, summaryInput, this.fastModel), this.scheduleChatForAutoDeletion(durableObjectName).catch((error) => {
72860
+ await Promise.all([summaryInput && summariseChatWithAI(this.sql.bind(this), durableObjectName, summaryInput, this.fastModel, this._logContext()), this.scheduleChatForAutoDeletion(durableObjectName).catch((error) => {
72782
72861
  logOperationalError("assistant.chat.reschedule.failed", error, {
72783
72862
  agentName: this.constructor.name,
72784
72863
  agentClass: this.constructor.name,
@@ -72878,9 +72957,9 @@ var Assistant = class extends Agent$1 {
72878
72957
  negativeFeedbackCount: chat.negative_feedback_count
72879
72958
  });
72880
72959
  } catch (error) {
72881
- console.error("[Assistant] Failed to sync chat to agent_chats", {
72882
- durableObjectName,
72883
- error
72960
+ logOperationalError("assistant.chat.sync.failed", error, {
72961
+ ...this._logContext(),
72962
+ attributes: { chatName: durableObjectName }
72884
72963
  });
72885
72964
  }
72886
72965
  }
@@ -72888,9 +72967,9 @@ var Assistant = class extends Agent$1 {
72888
72967
  try {
72889
72968
  await deleteGlobalChat(this.env.AGENTS_DB, this.constructor.name, this.name, durableObjectName);
72890
72969
  } catch (error) {
72891
- console.error("[Assistant] Failed to delete chat from agent_chats", {
72892
- durableObjectName,
72893
- error
72970
+ logOperationalError("assistant.chat.unlist.failed", error, {
72971
+ ...this._logContext(),
72972
+ attributes: { chatName: durableObjectName }
72894
72973
  });
72895
72974
  }
72896
72975
  }
@@ -73154,7 +73233,7 @@ function ensureFeedbackTableExists(sql) {
73154
73233
  PRIMARY KEY (message_id)
73155
73234
  )`;
73156
73235
  } catch (error) {
73157
- console.error("[Agent] Failed to create feedback table", error);
73236
+ logOperationalError("agent.feedback.table.failed", error);
73158
73237
  }
73159
73238
  }
73160
73239
  const MESSAGE_FEEDBACK_COMMENT_LIMIT = 2e3;
@@ -73185,7 +73264,7 @@ function submitMessageFeedback(sql, messageId, rating, comment, now = /* @__PURE
73185
73264
  comment = excluded.comment,
73186
73265
  updated_at = excluded.updated_at`;
73187
73266
  } catch (error) {
73188
- console.error("[Agent] Failed to submit message feedback", error);
73267
+ logOperationalError("agent.feedback.submit.failed", error, { attributes: { messageId } });
73189
73268
  }
73190
73269
  }
73191
73270
  function clearMessageFeedback(sql) {
@@ -73432,7 +73511,7 @@ var ChatAgent = class extends Agent {
73432
73511
  };
73433
73512
  }
73434
73513
  } catch (error) {
73435
- console.error("[ChatAgent] Failed to prepare model-only compaction", error);
73514
+ logOperationalError("agent.compaction.failed", error, this._logContext());
73436
73515
  }
73437
73516
  return config;
73438
73517
  }
@@ -73499,6 +73578,10 @@ var ChatAgent = class extends Agent {
73499
73578
  * The answer is the reply's text, or its JSON with `outputSchema`.
73500
73579
  */
73501
73580
  async run(input, body) {
73581
+ if (!await this._canChat()) return {
73582
+ status: "error",
73583
+ error: CHAT_REFUSED
73584
+ };
73502
73585
  const { inputSchema = z.string() } = await this.getAgentConfig(this.createToolContextForCurrentTurn(body));
73503
73586
  const check = checkInputValue(inputSchema, input);
73504
73587
  if (!check.success) return {
@@ -73580,9 +73663,7 @@ var ChatAgent = class extends Agent {
73580
73663
  await parent.recordChatFeedback(this.name, messageId, rating);
73581
73664
  } catch (error) {
73582
73665
  logOperationalError("agent.feedback.summary.failed", error, {
73583
- agentName: this.getTelemetryAgentName(),
73584
- agentClass: this.constructor.name,
73585
- durableObjectName: this.name,
73666
+ ...this._logContext(),
73586
73667
  attributes: { messageId }
73587
73668
  });
73588
73669
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@economic/agents",
3
- "version": "3.1.0",
3
+ "version": "3.2.1",
4
4
  "description": "A starter for creating a TypeScript package.",
5
5
  "license": "MIT",
6
6
  "files": [