@economic/agents 3.1.0 → 3.2.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 +19 -0
- package/dist/index.d.mts +18 -0
- package/dist/index.mjs +62 -3
- package/package.json +1 -1
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;
|
package/dist/index.mjs
CHANGED
|
@@ -71011,11 +71011,12 @@ var ToolApprovals = class {
|
|
|
71011
71011
|
}
|
|
71012
71012
|
};
|
|
71013
71013
|
// src/server/util/agent.ts
|
|
71014
|
-
function sendConnectionStatus(connection, status, reason) {
|
|
71014
|
+
function sendConnectionStatus(connection, status, reason, details) {
|
|
71015
71015
|
connection.send(JSON.stringify({
|
|
71016
71016
|
type: "connection_status",
|
|
71017
71017
|
status,
|
|
71018
|
-
reason
|
|
71018
|
+
reason,
|
|
71019
|
+
...details
|
|
71019
71020
|
}));
|
|
71020
71021
|
}
|
|
71021
71022
|
function sendAgentType(connection, agentType) {
|
|
@@ -71204,6 +71205,7 @@ const THINK_SKILLS_CONTEXT_LABEL = "think_skills";
|
|
|
71204
71205
|
const SESSION_CONTEXT_SEPARATOR = "═".repeat(46);
|
|
71205
71206
|
const TOOL_CONTINUATION_PREFIX = "economic:tool-continuation:";
|
|
71206
71207
|
const TOOL_CONTINUATION_REQUEST_PREFIX = "economic:tool-continuation-request:";
|
|
71208
|
+
const CHAT_REFUSED = "You can read this chat, but you can't chat in it.";
|
|
71207
71209
|
/** Whether a tool call's output is the paused or running output of an execution. */
|
|
71208
71210
|
function showsExecution(part, executionId) {
|
|
71209
71211
|
return "output" in part && part.output?.executionId === executionId;
|
|
@@ -71277,6 +71279,7 @@ var Agent = class Agent extends Think {
|
|
|
71277
71279
|
this._tracingHandlers = this.getTracingHandlers();
|
|
71278
71280
|
if (this.context.removeBlock(THINK_SKILLS_CONTEXT_LABEL)) await this.context.refreshSystemPrompt();
|
|
71279
71281
|
this._unfinishedToolCalls = await this._toolTaskSettlements.unfinishedToolCalls();
|
|
71282
|
+
this._refuseChatMessages();
|
|
71280
71283
|
this.registerInstance();
|
|
71281
71284
|
}
|
|
71282
71285
|
/**
|
|
@@ -71336,7 +71339,57 @@ var Agent = class Agent extends Think {
|
|
|
71336
71339
|
connection.close(closeCode, message);
|
|
71337
71340
|
return;
|
|
71338
71341
|
}
|
|
71339
|
-
sendConnectionStatus(connection, "connected");
|
|
71342
|
+
sendConnectionStatus(connection, "connected", void 0, { canChat: await this._askCanChat(connection) });
|
|
71343
|
+
}
|
|
71344
|
+
/**
|
|
71345
|
+
* Whether the user can send messages and answer approvals. When it's
|
|
71346
|
+
* `false` they can still read the chat. Asked for each connection in
|
|
71347
|
+
* `onConnect`, so an override of `onConnect` must call `super`. An error
|
|
71348
|
+
* counts as `false`. `true` by default.
|
|
71349
|
+
*/
|
|
71350
|
+
canChat(_userContext) {
|
|
71351
|
+
return true;
|
|
71352
|
+
}
|
|
71353
|
+
/** Answers still being asked, by connection, so messages sent meanwhile wait for them. */
|
|
71354
|
+
_canChatAnswers = /* @__PURE__ */ new Map();
|
|
71355
|
+
/**
|
|
71356
|
+
* Asks `canChat` for a connection. A no marks it readonly, which outlives
|
|
71357
|
+
* hibernation and the user's token.
|
|
71358
|
+
*/
|
|
71359
|
+
async _askCanChat(connection) {
|
|
71360
|
+
const answer = Promise.resolve().then(() => this.canChat(this.identity.userContext())).catch((error) => {
|
|
71361
|
+
logOperationalError("agent.can-chat.failed", error, this._logContext());
|
|
71362
|
+
return false;
|
|
71363
|
+
});
|
|
71364
|
+
this._canChatAnswers.set(connection.id, answer);
|
|
71365
|
+
const canChat = await answer;
|
|
71366
|
+
if (!canChat) this.setConnectionReadonly(connection, true);
|
|
71367
|
+
this._canChatAnswers.delete(connection.id);
|
|
71368
|
+
return canChat;
|
|
71369
|
+
}
|
|
71370
|
+
/** Whether the connection can chat. Calls from the server, without one, can. */
|
|
71371
|
+
async _canChat(connection = getCurrentAgent$1().connection) {
|
|
71372
|
+
if (!connection) return true;
|
|
71373
|
+
return await this._canChatAnswers.get(connection.id) ?? !this.isConnectionReadonly(connection);
|
|
71374
|
+
}
|
|
71375
|
+
/** Think stores a message before any hook runs, so it's refused before Think sees it. */
|
|
71376
|
+
_refuseChatMessages() {
|
|
71377
|
+
const onMessage = this.onMessage.bind(this);
|
|
71378
|
+
this.onMessage = async (connection, message) => {
|
|
71379
|
+
const event = typeof message === "string" ? parseProtocolMessage(message) : null;
|
|
71380
|
+
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);
|
|
71381
|
+
connection.send(JSON.stringify({
|
|
71382
|
+
type: CHAT_MESSAGE_TYPES.CHAT_MESSAGES,
|
|
71383
|
+
messages: this.messages
|
|
71384
|
+
}));
|
|
71385
|
+
if (event.type === "chat-request") connection.send(JSON.stringify({
|
|
71386
|
+
type: CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE,
|
|
71387
|
+
id: event.id,
|
|
71388
|
+
body: CHAT_REFUSED,
|
|
71389
|
+
done: true,
|
|
71390
|
+
error: true
|
|
71391
|
+
}));
|
|
71392
|
+
};
|
|
71340
71393
|
}
|
|
71341
71394
|
_turn = {
|
|
71342
71395
|
inputTools: {},
|
|
@@ -71518,11 +71571,13 @@ var Agent = class Agent extends Think {
|
|
|
71518
71571
|
}
|
|
71519
71572
|
/** `all` also approves the tool call's later approvals, so it doesn't stop again. */
|
|
71520
71573
|
async approveExecution(executionId, options) {
|
|
71574
|
+
if (!await this._canChat()) throw new Error(CHAT_REFUSED);
|
|
71521
71575
|
const answer = await this._toolApprovals.answer(executionId, true, { all: options?.all });
|
|
71522
71576
|
if (answer) return answer;
|
|
71523
71577
|
return super.approveExecution(executionId);
|
|
71524
71578
|
}
|
|
71525
71579
|
async rejectExecution(executionId, reason, options) {
|
|
71580
|
+
if (!await this._canChat()) throw new Error(CHAT_REFUSED);
|
|
71526
71581
|
const answer = await this._toolApprovals.answer(executionId, false, { autoContinue: options?.autoContinue });
|
|
71527
71582
|
if (answer) return answer;
|
|
71528
71583
|
return super.rejectExecution(executionId, reason, options);
|
|
@@ -73499,6 +73554,10 @@ var ChatAgent = class extends Agent {
|
|
|
73499
73554
|
* The answer is the reply's text, or its JSON with `outputSchema`.
|
|
73500
73555
|
*/
|
|
73501
73556
|
async run(input, body) {
|
|
73557
|
+
if (!await this._canChat()) return {
|
|
73558
|
+
status: "error",
|
|
73559
|
+
error: CHAT_REFUSED
|
|
73560
|
+
};
|
|
73502
73561
|
const { inputSchema = z.string() } = await this.getAgentConfig(this.createToolContextForCurrentTurn(body));
|
|
73503
73562
|
const check = checkInputValue(inputSchema, input);
|
|
73504
73563
|
if (!check.success) return {
|