@aws-blocks/bb-agent 0.1.3 → 0.3.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 +18 -19
- package/dist/agent.aws.d.ts +10 -0
- package/dist/agent.aws.d.ts.map +1 -1
- package/dist/agent.aws.js +12 -2
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +2 -1
- package/dist/agent.mock.js +1 -1
- package/dist/index.test.js +106 -5
- package/dist/models.d.ts +23 -13
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +27 -13
- package/dist/types.d.ts +25 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/src/agent.aws.ts +19 -2
- package/src/agent.mock.ts +1 -1
- package/src/agent.ts +2 -1
- package/src/index.test.ts +118 -5
- package/src/models.ts +29 -13
- package/src/types.ts +22 -6
- package/src/version.ts +1 -1
package/README.md
CHANGED
|
@@ -12,12 +12,11 @@ AI agent with streaming, tool calling, and conversation persistence. Powered by
|
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
14
|
import { Scope } from '@aws-blocks/core';
|
|
15
|
-
import { Agent
|
|
15
|
+
import { Agent } from '@aws-blocks/bb-agent';
|
|
16
16
|
|
|
17
17
|
const scope = new Scope('my-app');
|
|
18
18
|
|
|
19
19
|
const agent = new Agent(scope, 'support-agent', {
|
|
20
|
-
model: { deployed: BedrockModels.DEFAULT },
|
|
21
20
|
systemPrompt: 'You are a helpful support agent.',
|
|
22
21
|
});
|
|
23
22
|
|
|
@@ -30,7 +29,7 @@ const result = await agent.stream('Until when are you open tomorrow?', { convers
|
|
|
30
29
|
const done = await result.complete();
|
|
31
30
|
console.log(done.text); // "We're open until 6pm tomorrow."
|
|
32
31
|
```
|
|
33
|
-
|
|
32
|
+
Uses [`BedrockModels.BALANCED`](#bedrock-presets) (Claude Sonnet 4.6) by default. See [Model Configuration](#model-configuration) for other presets, [Tools](#tools) for adding capabilities, and [Local Development](#local-development) for running without AWS Bedrock.
|
|
34
33
|
|
|
35
34
|
## API
|
|
36
35
|
|
|
@@ -49,7 +48,7 @@ const agent = new Agent(scope, id, config)
|
|
|
49
48
|
| `getPendingInterrupts(conversationId)` | `Promise<Array<...>>` | Get unanswered interrupts (for reload support). |
|
|
50
49
|
| `getChannel(channelId)` | `Promise<RealtimeChannel>` | Get a Realtime channel for subscribing to chunks. |
|
|
51
50
|
|
|
52
|
-
`stream()` submits the message to AsyncJob and returns immediately — no API Gateway timeout risk. The agent runs asynchronously and publishes chunks to Realtime.
|
|
51
|
+
`stream()` submits the message to AsyncJob and returns immediately — no API Gateway timeout risk. The agent runs asynchronously and publishes chunks to Realtime. The channel ID is resolved as `options.channelId || options.conversationId || crypto.randomUUID()` — empty strings are treated as unset and fall through to the next value.
|
|
53
52
|
|
|
54
53
|
**Important: Subscribe before sending.** The agent starts emitting chunks immediately after `stream()` is called. If you subscribe to the channel after calling `stream()`, early chunks may be dropped. Always subscribe first, await `established`, then send:
|
|
55
54
|
|
|
@@ -140,7 +139,7 @@ The `useChat` hook only surfaces `user`, `assistant`, and `approval` messages to
|
|
|
140
139
|
|
|
141
140
|
### Model Configuration
|
|
142
141
|
|
|
143
|
-
|
|
142
|
+
Model configuration is optional. When omitted, the agent defaults to `BedrockModels.BALANCED` (Claude Sonnet 4.6) for deployment. Local development works out of the box — the canned provider (keyword-based mock) is used automatically when no local model is specified.
|
|
144
143
|
|
|
145
144
|
| Option | Type | Description |
|
|
146
145
|
|--------|------|-------------|
|
|
@@ -192,14 +191,14 @@ model: {
|
|
|
192
191
|
|
|
193
192
|
#### Bedrock Presets
|
|
194
193
|
|
|
195
|
-
Pre-configured model presets for quick setup. Names are capability-based so the underlying model can be upgraded without breaking your code. These use cross-region
|
|
194
|
+
Pre-configured model presets for quick setup. Names are capability-based so the underlying model can be upgraded without breaking your code. These use [global inference profiles](https://docs.aws.amazon.com/bedrock/latest/userguide/cross-region-inference.html) — requests may be routed to any supported AWS region for optimal throughput. If your workload has data residency requirements, specify a region-scoped inference profile explicitly instead of using a preset.
|
|
196
195
|
|
|
197
196
|
```typescript
|
|
198
197
|
import { Agent, BedrockModels} from '@aws-blocks/bb-agent';
|
|
199
198
|
|
|
200
199
|
const agent = new Agent(scope, 'agent', {
|
|
201
200
|
model: {
|
|
202
|
-
deployed: BedrockModels.
|
|
201
|
+
deployed: BedrockModels.BALANCED,
|
|
203
202
|
},
|
|
204
203
|
systemPrompt: '...',
|
|
205
204
|
});
|
|
@@ -207,15 +206,15 @@ const agent = new Agent(scope, 'agent', {
|
|
|
207
206
|
|
|
208
207
|
| Preset | Current Model | Notes |
|
|
209
208
|
|--------|---------------|-------|
|
|
210
|
-
| `BedrockModels.
|
|
211
|
-
| `BedrockModels.
|
|
212
|
-
| `BedrockModels.FAST` | `
|
|
213
|
-
|
|
214
|
-
|
|
209
|
+
| `BedrockModels.BALANCED` | `global.anthropic.claude-sonnet-4-6` | Great tool use, balanced cost. Recommended default for most workloads. |
|
|
210
|
+
| `BedrockModels.SMART` | `global.anthropic.claude-opus-4-8` | Highest capability for the hardest tasks. |
|
|
211
|
+
| `BedrockModels.FAST` | `global.anthropic.claude-haiku-4-5-20251001-v1:0` | Lowest latency, still strong capabilities. |
|
|
212
|
+
|
|
213
|
+
> **Migrating?** `DEFAULT` → `BALANCED` (or `SMART` for highest capability). `BUDGET`/`MICRO` → `FAST`. The old presets are still available but deprecated. Please consider upgrading!
|
|
215
214
|
|
|
216
215
|
Override inference settings with spread:
|
|
217
216
|
```typescript
|
|
218
|
-
model: { deployed: { ...BedrockModels.
|
|
217
|
+
model: { deployed: { ...BedrockModels.BALANCED, inferenceConfig: { temperature: 0.9, maxTokens: 8192 } } }
|
|
219
218
|
```
|
|
220
219
|
|
|
221
220
|
#### Ollama Presets
|
|
@@ -227,7 +226,7 @@ import { Agent, BedrockModels, OllamaModels} from '@aws-blocks/bb-agent';
|
|
|
227
226
|
|
|
228
227
|
const agent = new Agent(scope, 'agent', {
|
|
229
228
|
model: {
|
|
230
|
-
deployed: BedrockModels.
|
|
229
|
+
deployed: BedrockModels.BALANCED,
|
|
231
230
|
local: OllamaModels.SMALL,
|
|
232
231
|
},
|
|
233
232
|
systemPrompt: '...',
|
|
@@ -264,7 +263,7 @@ To see detailed health check logs, pass a logger with `info` level:
|
|
|
264
263
|
import { Logger } from '@aws-blocks/bb-logger';
|
|
265
264
|
|
|
266
265
|
const agent = new Agent(scope, 'agent', {
|
|
267
|
-
model: { deployed: BedrockModels.
|
|
266
|
+
model: { deployed: BedrockModels.BALANCED },
|
|
268
267
|
systemPrompt: '...',
|
|
269
268
|
logger: new Logger(scope, 'agent-log', { level: 'info' }),
|
|
270
269
|
});
|
|
@@ -543,7 +542,7 @@ The Agent BB works without a frontend — for scripts, background jobs, or serve
|
|
|
543
542
|
import { Agent, BedrockModels } from '@aws-blocks/bb-agent';
|
|
544
543
|
|
|
545
544
|
const agent = new Agent(scope, 'summarizer', {
|
|
546
|
-
model: { deployed: BedrockModels.
|
|
545
|
+
model: { deployed: BedrockModels.BALANCED },
|
|
547
546
|
systemPrompt: 'Summarize the input concisely.',
|
|
548
547
|
});
|
|
549
548
|
|
|
@@ -562,7 +561,7 @@ import { Agent, BedrockModels, InterruptError } from '@aws-blocks/bb-agent';
|
|
|
562
561
|
import { z } from 'zod';
|
|
563
562
|
|
|
564
563
|
const refundBot = new Agent(scope, 'refunds', {
|
|
565
|
-
model: { deployed: BedrockModels.
|
|
564
|
+
model: { deployed: BedrockModels.BALANCED },
|
|
566
565
|
systemPrompt: 'You process customer refund requests.',
|
|
567
566
|
tools: (tool) => ({
|
|
568
567
|
issueRefund: tool({
|
|
@@ -689,7 +688,7 @@ import { Agent, BedrockModels } from '@aws-blocks/bb-agent';
|
|
|
689
688
|
const scope = new Scope('my-app');
|
|
690
689
|
|
|
691
690
|
const agent = new Agent(scope, 'chat', {
|
|
692
|
-
model: { deployed: BedrockModels.
|
|
691
|
+
model: { deployed: BedrockModels.BALANCED },
|
|
693
692
|
systemPrompt: 'You are a helpful assistant.',
|
|
694
693
|
});
|
|
695
694
|
|
|
@@ -753,7 +752,7 @@ const scope = new Scope('my-app');
|
|
|
753
752
|
const kb = new KnowledgeBase(scope, 'docs', { source: './knowledge' });
|
|
754
753
|
|
|
755
754
|
const agent = new Agent(scope, 'support', {
|
|
756
|
-
model: { deployed: BedrockModels.
|
|
755
|
+
model: { deployed: BedrockModels.BALANCED },
|
|
757
756
|
systemPrompt: 'You are a customer support agent. Look up orders and search documentation to help the user.',
|
|
758
757
|
toolContextSchema: z.object({ userId: z.string() }),
|
|
759
758
|
tools: (tool) => ({
|
package/dist/agent.aws.d.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import type { ScopeParent } from '@aws-blocks/core';
|
|
2
|
+
import type { FileBucket } from '@aws-blocks/bb-file-bucket';
|
|
3
|
+
import type { SnapshotStorage } from '@strands-agents/sdk';
|
|
4
|
+
import type { S3StorageConfig } from '@strands-agents/sdk/session/s3-storage';
|
|
2
5
|
import { AgentBase } from './agent.js';
|
|
3
6
|
import type { AgentConfig, DefaultToolContext } from './types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Builds the deployed Agent's snapshot storage, pinning S3Storage to the Lambda
|
|
9
|
+
* execution region (`AWS_REGION`) so non-us-east-1 deploys use the correct regional
|
|
10
|
+
* endpoint (#120). `S3StorageImpl` is injectable so tests can assert the resulting
|
|
11
|
+
* config without depending on S3Storage/AWS SDK internals; production uses the real one.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createDeployedSnapshotStorage(bucket: FileBucket, S3StorageImpl?: new (config: S3StorageConfig) => SnapshotStorage): SnapshotStorage;
|
|
4
14
|
export declare class Agent<TContext = DefaultToolContext> extends AgentBase<TContext> {
|
|
5
15
|
constructor(scope: ScopeParent, id: string, config: AgentConfig<TContext>);
|
|
6
16
|
}
|
package/dist/agent.aws.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.aws.d.ts","sourceRoot":"","sources":["../src/agent.aws.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"agent.aws.d.ts","sourceRoot":"","sources":["../src/agent.aws.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AAC9E,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGlE;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC5C,MAAM,EAAE,UAAU,EAClB,aAAa,GAAE,KAAK,MAAM,EAAE,eAAe,KAAK,eAA2B,GACzE,eAAe,CAEjB;AAED,qBAAa,KAAK,CAAC,QAAQ,GAAG,kBAAkB,CAAE,SAAQ,SAAS,CAAC,QAAQ,CAAC;gBAChE,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC;CAGzE"}
|
package/dist/agent.aws.js
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
import { AgentBase } from './agent.js';
|
|
4
3
|
import { S3Storage } from '@strands-agents/sdk/session/s3-storage';
|
|
4
|
+
import { AgentBase } from './agent.js';
|
|
5
|
+
import { BedrockModels } from './models.js';
|
|
6
|
+
/**
|
|
7
|
+
* Builds the deployed Agent's snapshot storage, pinning S3Storage to the Lambda
|
|
8
|
+
* execution region (`AWS_REGION`) so non-us-east-1 deploys use the correct regional
|
|
9
|
+
* endpoint (#120). `S3StorageImpl` is injectable so tests can assert the resulting
|
|
10
|
+
* config without depending on S3Storage/AWS SDK internals; production uses the real one.
|
|
11
|
+
*/
|
|
12
|
+
export function createDeployedSnapshotStorage(bucket, S3StorageImpl = S3Storage) {
|
|
13
|
+
return new S3StorageImpl({ bucket: bucket.fullId, region: process.env.AWS_REGION });
|
|
14
|
+
}
|
|
5
15
|
export class Agent extends AgentBase {
|
|
6
16
|
constructor(scope, id, config) {
|
|
7
|
-
super(scope, id, config, config.model
|
|
17
|
+
super(scope, id, config, config.model?.deployed ?? BedrockModels.BALANCED, createDeployedSnapshotStorage);
|
|
8
18
|
}
|
|
9
19
|
}
|
package/dist/agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAA6C,MAAM,kBAAkB,CAAC;AACpF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAIpD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAMzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,KAAK,EAAE,WAAW,EAAoB,iBAAiB,EAAE,aAAa,EAAE,OAAO,EAAE,YAAY,EAAyC,WAAW,EAAa,iBAAiB,EAAE,kBAAkB,EAA6B,MAAM,YAAY,CAAC;AA+D1P;;;;;;;;;GASG;AACH,qBAAa,SAAS,CAAC,QAAQ,GAAG,kBAAkB,CAAE,SAAQ,KAAK;IAClE,4CAA4C;IAC5C,OAAO,CAAC,MAAM,CAAwB;IACtC,yFAAyF;IACzF,OAAO,CAAC,OAAO,CAAmC;IAClD,mCAAmC;IACnC,OAAO,CAAC,aAAa,CAAC,CAA8G;IACpI,6BAA6B;IAC7B,OAAO,CAAC,QAAQ,CAAC,CAA4G;IAC7H,oDAAoD;IACpD,OAAO,CAAC,EAAE,CAAgC;IAC1C,mFAAmF;IACnF,OAAO,CAAC,GAAG,CAA6C;IACxD,mCAAmC;IACnC,OAAO,CAAC,WAAW,CAA0C;IAC7D,wDAAwD;IACxD,OAAO,CAAC,eAAe,CAAkB;IACzC,+CAA+C;IAC/C,OAAO,CAAC,aAAa,CAAa;IAClC,2FAA2F;IAC3F,SAAS,CAAC,GAAG,EAAE,WAAW,CAAC;IAE3B;;;;;;OAMG;gBACS,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,EAAE,WAAW,EAAE,WAAW,GAAG,WAAW,EAAE,GAAG,SAAS,EAAE,qBAAqB,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,eAAe;IAgE/L;;;;;;;;OAQG;YACW,QAAQ;YA0GR,kBAAkB;IA+FhC;;;;;;;;OAQG;IACG,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}
|
package/dist/agent.js
CHANGED
|
@@ -377,7 +377,7 @@ export class AgentBase extends Scope {
|
|
|
377
377
|
*/
|
|
378
378
|
async stream(message, options) {
|
|
379
379
|
const conversationId = options?.conversationId;
|
|
380
|
-
const channelId = options?.channelId
|
|
380
|
+
const channelId = options?.channelId || conversationId || crypto.randomUUID();
|
|
381
381
|
if (!options?.userId && !this.config.inferenceOnly)
|
|
382
382
|
throw blocksAgentError(AgentErrors.PersistenceRequired, 'userId is required when persistence is enabled. Pass it via options.userId.');
|
|
383
383
|
const userId = options?.userId ?? 'anonymous';
|
|
@@ -405,6 +405,7 @@ export class AgentBase extends Scope {
|
|
|
405
405
|
}
|
|
406
406
|
});
|
|
407
407
|
}),
|
|
408
|
+
toJSON() { return { channelId, channel: null }; },
|
|
408
409
|
};
|
|
409
410
|
}
|
|
410
411
|
/**
|
package/dist/agent.mock.js
CHANGED
|
@@ -5,7 +5,7 @@ import { FileBucketSnapshotStorage } from './file-bucket-snapshot-storage.js';
|
|
|
5
5
|
export class Agent extends AgentBase {
|
|
6
6
|
constructor(scope, id, config) {
|
|
7
7
|
// Canned provider is appended as implicit last fallback for local dev
|
|
8
|
-
const local = config.model
|
|
8
|
+
const local = config.model?.local;
|
|
9
9
|
const candidates = local ? (Array.isArray(local) ? [...local, { provider: 'canned' }] : [local, { provider: 'canned' }]) : [{ provider: 'canned' }];
|
|
10
10
|
super(scope, id, config, candidates, (bucket) => new FileBucketSnapshotStorage(bucket));
|
|
11
11
|
}
|
package/dist/index.test.js
CHANGED
|
@@ -97,6 +97,34 @@ describe('needsApproval and interrupt mutual exclusivity', () => {
|
|
|
97
97
|
assert.ok(result.channelId);
|
|
98
98
|
});
|
|
99
99
|
});
|
|
100
|
+
// ── AgentStreamResult.toJSON() ───────────────────────────────────────────────
|
|
101
|
+
describe('AgentStreamResult.toJSON()', () => {
|
|
102
|
+
test('serializes to { channelId, channel: null }', async () => {
|
|
103
|
+
const scope = new Scope('test-tojson');
|
|
104
|
+
const agent = new Agent(scope, 'tj', { systemPrompt: 'test', model: { deployed: { provider: 'canned' }, local: { provider: 'canned' } } });
|
|
105
|
+
const result = await agent.stream('hello', { userId: 'test-user' });
|
|
106
|
+
const serialized = JSON.parse(JSON.stringify(result));
|
|
107
|
+
assert.deepStrictEqual(serialized, { channelId: result.channelId, channel: null });
|
|
108
|
+
assert.strictEqual('complete' in serialized, false);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
// ── stream() empty channelId fallback ────────────────────────────────────────
|
|
112
|
+
describe('stream() empty channelId fallback', () => {
|
|
113
|
+
test('empty channelId is treated as unset', async () => {
|
|
114
|
+
const scope = new Scope('test-empty-ch');
|
|
115
|
+
const agent = new Agent(scope, 'ec', { systemPrompt: 'test', model: { deployed: { provider: 'canned' }, local: { provider: 'canned' } } });
|
|
116
|
+
const result = await agent.stream('hello', { userId: 'test-user', channelId: '' });
|
|
117
|
+
assert.notStrictEqual(result.channelId, '');
|
|
118
|
+
assert.ok(result.channelId.length > 0);
|
|
119
|
+
});
|
|
120
|
+
test('empty conversationId is treated as unset', async () => {
|
|
121
|
+
const scope = new Scope('test-empty-conv');
|
|
122
|
+
const agent = new Agent(scope, 'ev', { systemPrompt: 'test', model: { deployed: { provider: 'canned' }, local: { provider: 'canned' } } });
|
|
123
|
+
const result = await agent.stream('hello', { userId: 'test-user', conversationId: '' });
|
|
124
|
+
assert.notStrictEqual(result.channelId, '');
|
|
125
|
+
assert.ok(result.channelId.length > 0);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
100
128
|
// ── tool factory enforcement (compile-time) ──────────────────────────────────
|
|
101
129
|
describe('tool factory enforcement', () => {
|
|
102
130
|
// Regression: AgentConfig.tools is a callback `(tool) => Record<string, AgentTool>`.
|
|
@@ -839,10 +867,17 @@ describe('checkModelHealth', () => {
|
|
|
839
867
|
});
|
|
840
868
|
});
|
|
841
869
|
// ── Model Presets ─────────────────────────────────────────────────────────────
|
|
870
|
+
describe('default model', () => {
|
|
871
|
+
test('agent can be created without model config', () => {
|
|
872
|
+
const s = new Scope('test-default-model');
|
|
873
|
+
const agent = new Agent(s, 'no-model', { systemPrompt: 'test' });
|
|
874
|
+
assert.ok(agent);
|
|
875
|
+
});
|
|
876
|
+
});
|
|
842
877
|
describe('BedrockModels presets', () => {
|
|
843
|
-
test('
|
|
844
|
-
assert.strictEqual(BedrockModels.
|
|
845
|
-
assert.ok(BedrockModels.
|
|
878
|
+
test('BALANCED resolves to a bedrock provider', async () => {
|
|
879
|
+
assert.strictEqual(BedrockModels.BALANCED.provider, 'bedrock');
|
|
880
|
+
assert.ok(BedrockModels.BALANCED.modelId);
|
|
846
881
|
});
|
|
847
882
|
test('all presets have provider bedrock and a modelId', () => {
|
|
848
883
|
for (const [name, config] of Object.entries(BedrockModels)) {
|
|
@@ -850,8 +885,8 @@ describe('BedrockModels presets', () => {
|
|
|
850
885
|
assert.ok(config.modelId, `${name} should have a modelId`);
|
|
851
886
|
}
|
|
852
887
|
});
|
|
853
|
-
test('
|
|
854
|
-
const model = await createStrandsModel(BedrockModels.
|
|
888
|
+
test('BALANCED flows through createStrandsModel to BedrockModel', async () => {
|
|
889
|
+
const model = await createStrandsModel(BedrockModels.BALANCED);
|
|
855
890
|
assert.ok(model, 'should create a model instance');
|
|
856
891
|
});
|
|
857
892
|
});
|
|
@@ -864,3 +899,69 @@ describe('OllamaModels presets', () => {
|
|
|
864
899
|
}
|
|
865
900
|
});
|
|
866
901
|
});
|
|
902
|
+
// ── deployed Agent S3Storage region (multi-region) ──────────────────────────
|
|
903
|
+
// Regression for #120: the deployed (aws-runtime) Agent must build Strands' S3Storage
|
|
904
|
+
// with the Lambda execution region (AWS_REGION). Omitting `region` defaults S3Storage to
|
|
905
|
+
// us-east-1 and breaks deploys elsewhere — the session bucket lives in the deploy region,
|
|
906
|
+
// so snapshots hit a cross-region 301 PermanentRedirect. index.test.ts otherwise only
|
|
907
|
+
// drives the mock/local path, so this covers agent.aws.ts by spying the S3Storage ctor.
|
|
908
|
+
import { Agent as DeployedAgent } from './index.aws.js';
|
|
909
|
+
import { createDeployedSnapshotStorage } from './agent.aws.js';
|
|
910
|
+
describe('deployed Agent S3Storage region (multi-region)', () => {
|
|
911
|
+
// Type-safe stand-in for S3Storage that records every config it's constructed with,
|
|
912
|
+
// so we assert exactly what agent.aws.ts passes in — no S3Storage/AWS SDK internals.
|
|
913
|
+
function s3StorageSpy() {
|
|
914
|
+
const configs = [];
|
|
915
|
+
class Spy {
|
|
916
|
+
constructor(config) {
|
|
917
|
+
configs.push(config);
|
|
918
|
+
}
|
|
919
|
+
async saveSnapshot() { }
|
|
920
|
+
async loadSnapshot() {
|
|
921
|
+
return null;
|
|
922
|
+
}
|
|
923
|
+
async listSnapshotIds() {
|
|
924
|
+
return [];
|
|
925
|
+
}
|
|
926
|
+
async deleteSession() { }
|
|
927
|
+
async loadManifest() {
|
|
928
|
+
return { schemaVersion: '1.0', updatedAt: new Date().toISOString() };
|
|
929
|
+
}
|
|
930
|
+
async saveManifest() { }
|
|
931
|
+
}
|
|
932
|
+
return { configs, Spy };
|
|
933
|
+
}
|
|
934
|
+
// Capture the S3StorageConfig the deployed factory builds for a given AWS_REGION.
|
|
935
|
+
function configForRegion(region, scopeId) {
|
|
936
|
+
const prev = process.env.AWS_REGION;
|
|
937
|
+
process.env.AWS_REGION = region;
|
|
938
|
+
try {
|
|
939
|
+
const { configs, Spy } = s3StorageSpy();
|
|
940
|
+
const bucket = new FileBucket(new Scope(scopeId), 'sn');
|
|
941
|
+
createDeployedSnapshotStorage(bucket, Spy);
|
|
942
|
+
assert.strictEqual(configs.length, 1, 'S3Storage should be constructed exactly once');
|
|
943
|
+
assert.strictEqual(configs[0].bucket, bucket.fullId, 'session bucket id should be passed through');
|
|
944
|
+
return configs[0];
|
|
945
|
+
}
|
|
946
|
+
finally {
|
|
947
|
+
if (prev === undefined)
|
|
948
|
+
delete process.env.AWS_REGION;
|
|
949
|
+
else
|
|
950
|
+
process.env.AWS_REGION = prev;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
test('constructs S3Storage with the Lambda execution region (eu-west-1)', () => {
|
|
954
|
+
const config = configForRegion('eu-west-1', 'test-s3-region-euw1');
|
|
955
|
+
assert.strictEqual(config.region, 'eu-west-1');
|
|
956
|
+
assert.notStrictEqual(config.region, 'us-east-1');
|
|
957
|
+
});
|
|
958
|
+
test('does not hard-pin us-east-1 when deployed to another region (ap-southeast-2)', () => {
|
|
959
|
+
const config = configForRegion('ap-southeast-2', 'test-s3-region-apse2');
|
|
960
|
+
assert.strictEqual(config.region, 'ap-southeast-2');
|
|
961
|
+
assert.notStrictEqual(config.region, 'us-east-1');
|
|
962
|
+
});
|
|
963
|
+
test('deployed Agent constructs on the aws-runtime path', () => {
|
|
964
|
+
const agent = new DeployedAgent(new Scope('test-s3-agent'), 'r', { systemPrompt: 'test', model: { deployed: { provider: 'canned' } } });
|
|
965
|
+
assert.ok(agent);
|
|
966
|
+
});
|
|
967
|
+
});
|
package/dist/models.d.ts
CHANGED
|
@@ -1,32 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pre-configured Bedrock model presets using
|
|
2
|
+
* Pre-configured Bedrock model presets using global inference profiles.
|
|
3
3
|
* Names are capability-based so the underlying model can be upgraded without breaking user code.
|
|
4
|
+
*
|
|
5
|
+
* **Note:** Global inference profiles route requests to any supported AWS region for
|
|
6
|
+
* optimal throughput. If your workload has data residency requirements, specify a
|
|
7
|
+
* region-scoped inference profile explicitly.
|
|
8
|
+
* @see https://docs.aws.amazon.com/bedrock/latest/userguide/cross-region-inference.html
|
|
4
9
|
*/
|
|
5
10
|
export declare const BedrockModels: {
|
|
6
|
-
/**
|
|
7
|
-
readonly
|
|
11
|
+
/** Great tool use, balanced cost — good middle tier for most workloads. Currently: Claude Sonnet 4.6. */
|
|
12
|
+
readonly BALANCED: {
|
|
8
13
|
readonly provider: "bedrock";
|
|
9
|
-
readonly modelId: "
|
|
14
|
+
readonly modelId: "global.anthropic.claude-sonnet-4-6";
|
|
10
15
|
};
|
|
11
|
-
/**
|
|
12
|
-
readonly
|
|
16
|
+
/** Highest capability for the hardest tasks. Currently: Claude Opus 4.8. */
|
|
17
|
+
readonly SMART: {
|
|
13
18
|
readonly provider: "bedrock";
|
|
14
|
-
readonly modelId: "
|
|
19
|
+
readonly modelId: "global.anthropic.claude-opus-4-8";
|
|
15
20
|
};
|
|
16
|
-
/**
|
|
21
|
+
/** Lowest latency, still strong capabilities. Currently: Claude Haiku 4.5. */
|
|
17
22
|
readonly FAST: {
|
|
18
23
|
readonly provider: "bedrock";
|
|
19
|
-
readonly modelId: "
|
|
24
|
+
readonly modelId: "global.anthropic.claude-haiku-4-5-20251001-v1:0";
|
|
25
|
+
};
|
|
26
|
+
/** @deprecated Use `BedrockModels.BALANCED` instead. */
|
|
27
|
+
readonly DEFAULT: {
|
|
28
|
+
readonly provider: "bedrock";
|
|
29
|
+
readonly modelId: "global.anthropic.claude-sonnet-4-6";
|
|
20
30
|
};
|
|
21
|
-
/**
|
|
31
|
+
/** @deprecated Use `BedrockModels.FAST` instead. */
|
|
22
32
|
readonly BUDGET: {
|
|
23
33
|
readonly provider: "bedrock";
|
|
24
|
-
readonly modelId: "
|
|
34
|
+
readonly modelId: "global.anthropic.claude-haiku-4-5-20251001-v1:0";
|
|
25
35
|
};
|
|
26
|
-
/**
|
|
36
|
+
/** @deprecated Use `BedrockModels.FAST` instead. */
|
|
27
37
|
readonly MICRO: {
|
|
28
38
|
readonly provider: "bedrock";
|
|
29
|
-
readonly modelId: "
|
|
39
|
+
readonly modelId: "global.anthropic.claude-haiku-4-5-20251001-v1:0";
|
|
30
40
|
};
|
|
31
41
|
};
|
|
32
42
|
/**
|
package/dist/models.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AASA;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa;IACzB,yGAAyG;;;;;IAKzG,4EAA4E;;;;;IAK5E,8EAA8E;;;;;IAM9E,wDAAwD;;;;;IAKxD,oDAAoD;;;;;IAKpD,oDAAoD;;;;;CAKL,CAAC;AAEjD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,YAAY;IACxB,kGAAkG;;;;;;;IAOlG,gGAAgG;;;;;;;IAOhG,+FAA+F;;;;;;;IAQ/F,4FAA4F;;;;;;;IAO5F,iFAAiF;;;;;;;CAOlC,CAAC"}
|
package/dist/models.js
CHANGED
|
@@ -1,34 +1,47 @@
|
|
|
1
1
|
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
const BALANCED_MODEL_ID = 'global.anthropic.claude-sonnet-4-6';
|
|
4
|
+
const SMART_MODEL_ID = 'global.anthropic.claude-opus-4-8';
|
|
5
|
+
const FAST_MODEL_ID = 'global.anthropic.claude-haiku-4-5-20251001-v1:0';
|
|
3
6
|
/**
|
|
4
|
-
* Pre-configured Bedrock model presets using
|
|
7
|
+
* Pre-configured Bedrock model presets using global inference profiles.
|
|
5
8
|
* Names are capability-based so the underlying model can be upgraded without breaking user code.
|
|
9
|
+
*
|
|
10
|
+
* **Note:** Global inference profiles route requests to any supported AWS region for
|
|
11
|
+
* optimal throughput. If your workload has data residency requirements, specify a
|
|
12
|
+
* region-scoped inference profile explicitly.
|
|
13
|
+
* @see https://docs.aws.amazon.com/bedrock/latest/userguide/cross-region-inference.html
|
|
6
14
|
*/
|
|
7
15
|
export const BedrockModels = {
|
|
8
|
-
/**
|
|
9
|
-
|
|
16
|
+
/** Great tool use, balanced cost — good middle tier for most workloads. Currently: Claude Sonnet 4.6. */
|
|
17
|
+
BALANCED: {
|
|
10
18
|
provider: 'bedrock',
|
|
11
|
-
modelId:
|
|
19
|
+
modelId: BALANCED_MODEL_ID,
|
|
12
20
|
},
|
|
13
|
-
/**
|
|
14
|
-
|
|
21
|
+
/** Highest capability for the hardest tasks. Currently: Claude Opus 4.8. */
|
|
22
|
+
SMART: {
|
|
15
23
|
provider: 'bedrock',
|
|
16
|
-
modelId:
|
|
24
|
+
modelId: SMART_MODEL_ID,
|
|
17
25
|
},
|
|
18
|
-
/**
|
|
26
|
+
/** Lowest latency, still strong capabilities. Currently: Claude Haiku 4.5. */
|
|
19
27
|
FAST: {
|
|
20
28
|
provider: 'bedrock',
|
|
21
|
-
modelId:
|
|
29
|
+
modelId: FAST_MODEL_ID,
|
|
30
|
+
},
|
|
31
|
+
/** @deprecated Use `BedrockModels.BALANCED` instead. */
|
|
32
|
+
DEFAULT: {
|
|
33
|
+
provider: 'bedrock',
|
|
34
|
+
modelId: BALANCED_MODEL_ID,
|
|
22
35
|
},
|
|
23
|
-
/**
|
|
36
|
+
/** @deprecated Use `BedrockModels.FAST` instead. */
|
|
24
37
|
BUDGET: {
|
|
25
38
|
provider: 'bedrock',
|
|
26
|
-
modelId:
|
|
39
|
+
modelId: FAST_MODEL_ID,
|
|
27
40
|
},
|
|
28
|
-
/**
|
|
41
|
+
/** @deprecated Use `BedrockModels.FAST` instead. */
|
|
29
42
|
MICRO: {
|
|
30
43
|
provider: 'bedrock',
|
|
31
|
-
modelId:
|
|
44
|
+
modelId: FAST_MODEL_ID,
|
|
32
45
|
},
|
|
33
46
|
};
|
|
34
47
|
/**
|
|
@@ -61,6 +74,7 @@ export const OllamaModels = {
|
|
|
61
74
|
apiKey: 'ollama',
|
|
62
75
|
},
|
|
63
76
|
/** Strong reasoning at moderate size. Currently: DeepSeek R1 14B (~9 GB, needs 16 GB VRAM). */
|
|
77
|
+
// TODO: DeepSeek R1 is strong at reasoning but weak at tool calling — swap to a more tool-capable model.
|
|
64
78
|
MEDIUM: {
|
|
65
79
|
provider: 'openai-api',
|
|
66
80
|
modelId: 'deepseek-r1:14b',
|
package/dist/types.d.ts
CHANGED
|
@@ -41,9 +41,9 @@ export interface AgentConfig<TContext = DefaultToolContext> {
|
|
|
41
41
|
* The agent does inference + tools only — no conversation history.
|
|
42
42
|
*/
|
|
43
43
|
inferenceOnly?: boolean;
|
|
44
|
-
model
|
|
45
|
-
/** Model(s) for AWS deployment. Tries candidates in order; throws if all fail. */
|
|
46
|
-
deployed
|
|
44
|
+
model?: {
|
|
45
|
+
/** Model(s) for AWS deployment. Tries candidates in order; throws if all fail. Defaults to BedrockModels.BALANCED. */
|
|
46
|
+
deployed?: ModelConfig | ModelConfig[];
|
|
47
47
|
/** Model(s) for local development. Tries candidates in order; canned is implicit last fallback. */
|
|
48
48
|
local?: ModelConfig | ModelConfig[];
|
|
49
49
|
};
|
|
@@ -235,7 +235,7 @@ export interface ToolCallRecord {
|
|
|
235
235
|
}
|
|
236
236
|
export interface StreamOptions<TContext = DefaultToolContext> {
|
|
237
237
|
conversationId?: string;
|
|
238
|
-
/** Channel ID for Realtime delivery. Defaults to conversationId or a random UUID. */
|
|
238
|
+
/** Channel ID for Realtime delivery. Defaults to conversationId or a random UUID. Empty strings are treated as unset. */
|
|
239
239
|
channelId?: string;
|
|
240
240
|
/** User ID for conversation scoping. Defaults to 'anonymous'. */
|
|
241
241
|
userId?: string;
|
|
@@ -247,14 +247,33 @@ export interface StreamOptions<TContext = DefaultToolContext> {
|
|
|
247
247
|
*/
|
|
248
248
|
context?: TContext;
|
|
249
249
|
}
|
|
250
|
-
/**
|
|
250
|
+
/**
|
|
251
|
+
* Returned by stream(). Provides the channelId and server-side convenience methods.
|
|
252
|
+
*
|
|
253
|
+
* Safe to return directly from API methods — `toJSON()` serializes to
|
|
254
|
+
* `{ channelId, channel: null }`. Only `channelId` is meaningful client-side;
|
|
255
|
+
* `channel` is explicitly `null` to signal the live handle is server-side only,
|
|
256
|
+
* and the `complete()` helper is dropped (functions don't serialize).
|
|
257
|
+
*/
|
|
251
258
|
export interface AgentStreamResult {
|
|
252
259
|
/** Realtime channel ID where chunks are published. */
|
|
253
260
|
channelId: string;
|
|
254
|
-
/**
|
|
261
|
+
/**
|
|
262
|
+
* Realtime channel handle (server-side only). Nulled by `toJSON()` — clients subscribe from `channelId` instead.
|
|
263
|
+
*
|
|
264
|
+
* @remarks
|
|
265
|
+
* Unlike `RealtimeChannel.toJSON()` which produces a hydratable descriptor, this is nulled
|
|
266
|
+
* because it's a `Promise` that can't round-trip. Clients reconstruct a subscribe-only
|
|
267
|
+
* channel from `channelId` via the `useChat` `subscribe` callback.
|
|
268
|
+
*/
|
|
255
269
|
channel: Promise<RealtimeChannel<AgentStreamChunk>>;
|
|
256
270
|
/** Wait for the complete response (server-side). Resolves when the done chunk arrives. */
|
|
257
271
|
complete: () => Promise<AgentStreamChunk>;
|
|
272
|
+
/** Only `{ channelId, channel: null }` is serialized when this object crosses the RPC boundary. */
|
|
273
|
+
toJSON(): {
|
|
274
|
+
channelId: string;
|
|
275
|
+
channel: null;
|
|
276
|
+
};
|
|
258
277
|
}
|
|
259
278
|
export interface AgentStreamChunk {
|
|
260
279
|
type: 'text-delta' | 'tool-call' | 'tool-result' | 'done' | 'error' | 'interrupt';
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEzD,mCAAmC;AACnC,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GAAG,SAAS,EAAE,CAAC;AAEtG,MAAM,WAAW,WAAW;IAC3B;;;;OAIG;IACH,QAAQ,EAAE,SAAS,GAAG,YAAY,GAAG,QAAQ,CAAC;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qJAAqJ;IACrJ,MAAM,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC1C,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC9B;AAED,MAAM,WAAW,eAAe;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB;IAChC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAErD,MAAM,WAAW,WAAW,CAAC,QAAQ,GAAG,kBAAkB;IACzD;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,KAAK,EAAE;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aws-blocks/bb-agent",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"author": "Amazon Web Services",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@aws-blocks/bb-file-bucket": "^0.1.2",
|
|
40
40
|
"@aws-blocks/bb-logger": "^0.1.2",
|
|
41
41
|
"@aws-blocks/bb-realtime": "^0.1.2",
|
|
42
|
-
"@aws-blocks/core": "^0.1.
|
|
42
|
+
"@aws-blocks/core": "^0.1.10",
|
|
43
43
|
"@aws-sdk/client-bedrock": "^3.700.0",
|
|
44
44
|
"@strands-agents/sdk": "~1.3.0",
|
|
45
45
|
"openai": "^6.7.0",
|
package/src/agent.aws.ts
CHANGED
|
@@ -2,12 +2,29 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
4
|
import type { ScopeParent } from '@aws-blocks/core';
|
|
5
|
-
import {
|
|
5
|
+
import type { FileBucket } from '@aws-blocks/bb-file-bucket';
|
|
6
|
+
import type { SnapshotStorage } from '@strands-agents/sdk';
|
|
6
7
|
import { S3Storage } from '@strands-agents/sdk/session/s3-storage';
|
|
8
|
+
import type { S3StorageConfig } from '@strands-agents/sdk/session/s3-storage';
|
|
9
|
+
import { AgentBase } from './agent.js';
|
|
7
10
|
import type { AgentConfig, DefaultToolContext } from './types.js';
|
|
11
|
+
import { BedrockModels } from './models.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Builds the deployed Agent's snapshot storage, pinning S3Storage to the Lambda
|
|
15
|
+
* execution region (`AWS_REGION`) so non-us-east-1 deploys use the correct regional
|
|
16
|
+
* endpoint (#120). `S3StorageImpl` is injectable so tests can assert the resulting
|
|
17
|
+
* config without depending on S3Storage/AWS SDK internals; production uses the real one.
|
|
18
|
+
*/
|
|
19
|
+
export function createDeployedSnapshotStorage(
|
|
20
|
+
bucket: FileBucket,
|
|
21
|
+
S3StorageImpl: new (config: S3StorageConfig) => SnapshotStorage = S3Storage,
|
|
22
|
+
): SnapshotStorage {
|
|
23
|
+
return new S3StorageImpl({ bucket: bucket.fullId, region: process.env.AWS_REGION });
|
|
24
|
+
}
|
|
8
25
|
|
|
9
26
|
export class Agent<TContext = DefaultToolContext> extends AgentBase<TContext> {
|
|
10
27
|
constructor(scope: ScopeParent, id: string, config: AgentConfig<TContext>) {
|
|
11
|
-
super(scope, id, config, config.model
|
|
28
|
+
super(scope, id, config, config.model?.deployed ?? BedrockModels.BALANCED, createDeployedSnapshotStorage);
|
|
12
29
|
}
|
|
13
30
|
}
|
package/src/agent.mock.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type { AgentConfig, DefaultToolContext } from './types.js';
|
|
|
9
9
|
export class Agent<TContext = DefaultToolContext> extends AgentBase<TContext> {
|
|
10
10
|
constructor(scope: ScopeParent, id: string, config: AgentConfig<TContext>) {
|
|
11
11
|
// Canned provider is appended as implicit last fallback for local dev
|
|
12
|
-
const local = config.model
|
|
12
|
+
const local = config.model?.local;
|
|
13
13
|
const candidates = local ? (Array.isArray(local) ? [...local, { provider: 'canned' as const }] : [local, { provider: 'canned' as const }]) : [{ provider: 'canned' as const }];
|
|
14
14
|
super(scope, id, config, candidates, (bucket) => new FileBucketSnapshotStorage(bucket));
|
|
15
15
|
}
|
package/src/agent.ts
CHANGED
|
@@ -403,7 +403,7 @@ export class AgentBase<TContext = DefaultToolContext> extends Scope {
|
|
|
403
403
|
*/
|
|
404
404
|
async stream(message: string, options?: StreamOptions<TContext>): Promise<AgentStreamResult> {
|
|
405
405
|
const conversationId = options?.conversationId;
|
|
406
|
-
const channelId = options?.channelId
|
|
406
|
+
const channelId = options?.channelId || conversationId || crypto.randomUUID();
|
|
407
407
|
if (!options?.userId && !this.config.inferenceOnly) throw blocksAgentError(AgentErrors.PersistenceRequired, 'userId is required when persistence is enabled. Pass it via options.userId.');
|
|
408
408
|
const userId = options?.userId ?? 'anonymous';
|
|
409
409
|
const context = this.resolveContext(options?.context);
|
|
@@ -428,6 +428,7 @@ export class AgentBase<TContext = DefaultToolContext> extends Scope {
|
|
|
428
428
|
}
|
|
429
429
|
});
|
|
430
430
|
}),
|
|
431
|
+
toJSON() { return { channelId, channel: null }; },
|
|
431
432
|
};
|
|
432
433
|
}
|
|
433
434
|
|
package/src/index.test.ts
CHANGED
|
@@ -115,6 +115,39 @@ describe('needsApproval and interrupt mutual exclusivity', () => {
|
|
|
115
115
|
});
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
+
// ── AgentStreamResult.toJSON() ───────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
describe('AgentStreamResult.toJSON()', () => {
|
|
121
|
+
test('serializes to { channelId, channel: null }', async () => {
|
|
122
|
+
const scope = new Scope('test-tojson');
|
|
123
|
+
const agent = new Agent(scope, 'tj', { systemPrompt: 'test', model: { deployed: { provider: 'canned' }, local: { provider: 'canned' } } });
|
|
124
|
+
const result = await agent.stream('hello', { userId: 'test-user' });
|
|
125
|
+
const serialized = JSON.parse(JSON.stringify(result));
|
|
126
|
+
assert.deepStrictEqual(serialized, { channelId: result.channelId, channel: null });
|
|
127
|
+
assert.strictEqual('complete' in serialized, false);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// ── stream() empty channelId fallback ────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
describe('stream() empty channelId fallback', () => {
|
|
134
|
+
test('empty channelId is treated as unset', async () => {
|
|
135
|
+
const scope = new Scope('test-empty-ch');
|
|
136
|
+
const agent = new Agent(scope, 'ec', { systemPrompt: 'test', model: { deployed: { provider: 'canned' }, local: { provider: 'canned' } } });
|
|
137
|
+
const result = await agent.stream('hello', { userId: 'test-user', channelId: '' });
|
|
138
|
+
assert.notStrictEqual(result.channelId, '');
|
|
139
|
+
assert.ok(result.channelId.length > 0);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('empty conversationId is treated as unset', async () => {
|
|
143
|
+
const scope = new Scope('test-empty-conv');
|
|
144
|
+
const agent = new Agent(scope, 'ev', { systemPrompt: 'test', model: { deployed: { provider: 'canned' }, local: { provider: 'canned' } } });
|
|
145
|
+
const result = await agent.stream('hello', { userId: 'test-user', conversationId: '' });
|
|
146
|
+
assert.notStrictEqual(result.channelId, '');
|
|
147
|
+
assert.ok(result.channelId.length > 0);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
118
151
|
// ── tool factory enforcement (compile-time) ──────────────────────────────────
|
|
119
152
|
|
|
120
153
|
describe('tool factory enforcement', () => {
|
|
@@ -983,10 +1016,18 @@ describe('checkModelHealth', () => {
|
|
|
983
1016
|
|
|
984
1017
|
// ── Model Presets ─────────────────────────────────────────────────────────────
|
|
985
1018
|
|
|
1019
|
+
describe('default model', () => {
|
|
1020
|
+
test('agent can be created without model config', () => {
|
|
1021
|
+
const s = new Scope('test-default-model');
|
|
1022
|
+
const agent = new Agent(s, 'no-model', { systemPrompt: 'test' });
|
|
1023
|
+
assert.ok(agent);
|
|
1024
|
+
});
|
|
1025
|
+
});
|
|
1026
|
+
|
|
986
1027
|
describe('BedrockModels presets', () => {
|
|
987
|
-
test('
|
|
988
|
-
assert.strictEqual(BedrockModels.
|
|
989
|
-
assert.ok(BedrockModels.
|
|
1028
|
+
test('BALANCED resolves to a bedrock provider', async () => {
|
|
1029
|
+
assert.strictEqual(BedrockModels.BALANCED.provider, 'bedrock');
|
|
1030
|
+
assert.ok(BedrockModels.BALANCED.modelId);
|
|
990
1031
|
});
|
|
991
1032
|
|
|
992
1033
|
test('all presets have provider bedrock and a modelId', () => {
|
|
@@ -996,8 +1037,8 @@ describe('BedrockModels presets', () => {
|
|
|
996
1037
|
}
|
|
997
1038
|
});
|
|
998
1039
|
|
|
999
|
-
test('
|
|
1000
|
-
const model = await createStrandsModel(BedrockModels.
|
|
1040
|
+
test('BALANCED flows through createStrandsModel to BedrockModel', async () => {
|
|
1041
|
+
const model = await createStrandsModel(BedrockModels.BALANCED);
|
|
1001
1042
|
assert.ok(model, 'should create a model instance');
|
|
1002
1043
|
});
|
|
1003
1044
|
});
|
|
@@ -1011,3 +1052,75 @@ describe('OllamaModels presets', () => {
|
|
|
1011
1052
|
}
|
|
1012
1053
|
});
|
|
1013
1054
|
});
|
|
1055
|
+
|
|
1056
|
+
// ── deployed Agent S3Storage region (multi-region) ──────────────────────────
|
|
1057
|
+
// Regression for #120: the deployed (aws-runtime) Agent must build Strands' S3Storage
|
|
1058
|
+
// with the Lambda execution region (AWS_REGION). Omitting `region` defaults S3Storage to
|
|
1059
|
+
// us-east-1 and breaks deploys elsewhere — the session bucket lives in the deploy region,
|
|
1060
|
+
// so snapshots hit a cross-region 301 PermanentRedirect. index.test.ts otherwise only
|
|
1061
|
+
// drives the mock/local path, so this covers agent.aws.ts by spying the S3Storage ctor.
|
|
1062
|
+
|
|
1063
|
+
import { Agent as DeployedAgent } from './index.aws.js';
|
|
1064
|
+
import { createDeployedSnapshotStorage } from './agent.aws.js';
|
|
1065
|
+
import type { SnapshotStorage, SnapshotManifest } from '@strands-agents/sdk';
|
|
1066
|
+
import type { S3StorageConfig } from '@strands-agents/sdk/session/s3-storage';
|
|
1067
|
+
|
|
1068
|
+
describe('deployed Agent S3Storage region (multi-region)', () => {
|
|
1069
|
+
// Type-safe stand-in for S3Storage that records every config it's constructed with,
|
|
1070
|
+
// so we assert exactly what agent.aws.ts passes in — no S3Storage/AWS SDK internals.
|
|
1071
|
+
function s3StorageSpy() {
|
|
1072
|
+
const configs: S3StorageConfig[] = [];
|
|
1073
|
+
class Spy implements SnapshotStorage {
|
|
1074
|
+
constructor(config: S3StorageConfig) {
|
|
1075
|
+
configs.push(config);
|
|
1076
|
+
}
|
|
1077
|
+
async saveSnapshot(): Promise<void> {}
|
|
1078
|
+
async loadSnapshot(): Promise<null> {
|
|
1079
|
+
return null;
|
|
1080
|
+
}
|
|
1081
|
+
async listSnapshotIds(): Promise<string[]> {
|
|
1082
|
+
return [];
|
|
1083
|
+
}
|
|
1084
|
+
async deleteSession(): Promise<void> {}
|
|
1085
|
+
async loadManifest(): Promise<SnapshotManifest> {
|
|
1086
|
+
return { schemaVersion: '1.0', updatedAt: new Date().toISOString() };
|
|
1087
|
+
}
|
|
1088
|
+
async saveManifest(): Promise<void> {}
|
|
1089
|
+
}
|
|
1090
|
+
return { configs, Spy };
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// Capture the S3StorageConfig the deployed factory builds for a given AWS_REGION.
|
|
1094
|
+
function configForRegion(region: string, scopeId: string): S3StorageConfig {
|
|
1095
|
+
const prev = process.env.AWS_REGION;
|
|
1096
|
+
process.env.AWS_REGION = region;
|
|
1097
|
+
try {
|
|
1098
|
+
const { configs, Spy } = s3StorageSpy();
|
|
1099
|
+
const bucket = new FileBucket(new Scope(scopeId), 'sn');
|
|
1100
|
+
createDeployedSnapshotStorage(bucket, Spy);
|
|
1101
|
+
assert.strictEqual(configs.length, 1, 'S3Storage should be constructed exactly once');
|
|
1102
|
+
assert.strictEqual(configs[0].bucket, bucket.fullId, 'session bucket id should be passed through');
|
|
1103
|
+
return configs[0];
|
|
1104
|
+
} finally {
|
|
1105
|
+
if (prev === undefined) delete process.env.AWS_REGION;
|
|
1106
|
+
else process.env.AWS_REGION = prev;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
test('constructs S3Storage with the Lambda execution region (eu-west-1)', () => {
|
|
1111
|
+
const config = configForRegion('eu-west-1', 'test-s3-region-euw1');
|
|
1112
|
+
assert.strictEqual(config.region, 'eu-west-1');
|
|
1113
|
+
assert.notStrictEqual(config.region, 'us-east-1');
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
test('does not hard-pin us-east-1 when deployed to another region (ap-southeast-2)', () => {
|
|
1117
|
+
const config = configForRegion('ap-southeast-2', 'test-s3-region-apse2');
|
|
1118
|
+
assert.strictEqual(config.region, 'ap-southeast-2');
|
|
1119
|
+
assert.notStrictEqual(config.region, 'us-east-1');
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
test('deployed Agent constructs on the aws-runtime path', () => {
|
|
1123
|
+
const agent = new DeployedAgent(new Scope('test-s3-agent'), 'r', { systemPrompt: 'test', model: { deployed: { provider: 'canned' } } });
|
|
1124
|
+
assert.ok(agent);
|
|
1125
|
+
});
|
|
1126
|
+
});
|
package/src/models.ts
CHANGED
|
@@ -3,35 +3,50 @@
|
|
|
3
3
|
|
|
4
4
|
import type { ModelConfig } from './types.js';
|
|
5
5
|
|
|
6
|
+
const BALANCED_MODEL_ID = 'global.anthropic.claude-sonnet-4-6';
|
|
7
|
+
const SMART_MODEL_ID = 'global.anthropic.claude-opus-4-8';
|
|
8
|
+
const FAST_MODEL_ID = 'global.anthropic.claude-haiku-4-5-20251001-v1:0';
|
|
9
|
+
|
|
6
10
|
/**
|
|
7
|
-
* Pre-configured Bedrock model presets using
|
|
11
|
+
* Pre-configured Bedrock model presets using global inference profiles.
|
|
8
12
|
* Names are capability-based so the underlying model can be upgraded without breaking user code.
|
|
13
|
+
*
|
|
14
|
+
* **Note:** Global inference profiles route requests to any supported AWS region for
|
|
15
|
+
* optimal throughput. If your workload has data residency requirements, specify a
|
|
16
|
+
* region-scoped inference profile explicitly.
|
|
17
|
+
* @see https://docs.aws.amazon.com/bedrock/latest/userguide/cross-region-inference.html
|
|
9
18
|
*/
|
|
10
19
|
export const BedrockModels = {
|
|
11
|
-
/**
|
|
12
|
-
|
|
20
|
+
/** Great tool use, balanced cost — good middle tier for most workloads. Currently: Claude Sonnet 4.6. */
|
|
21
|
+
BALANCED: {
|
|
13
22
|
provider: 'bedrock',
|
|
14
|
-
modelId:
|
|
23
|
+
modelId: BALANCED_MODEL_ID,
|
|
15
24
|
},
|
|
16
|
-
/**
|
|
17
|
-
|
|
25
|
+
/** Highest capability for the hardest tasks. Currently: Claude Opus 4.8. */
|
|
26
|
+
SMART: {
|
|
18
27
|
provider: 'bedrock',
|
|
19
|
-
modelId:
|
|
28
|
+
modelId: SMART_MODEL_ID,
|
|
20
29
|
},
|
|
21
|
-
/**
|
|
30
|
+
/** Lowest latency, still strong capabilities. Currently: Claude Haiku 4.5. */
|
|
22
31
|
FAST: {
|
|
23
32
|
provider: 'bedrock',
|
|
24
|
-
modelId:
|
|
33
|
+
modelId: FAST_MODEL_ID,
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
/** @deprecated Use `BedrockModels.BALANCED` instead. */
|
|
37
|
+
DEFAULT: {
|
|
38
|
+
provider: 'bedrock',
|
|
39
|
+
modelId: BALANCED_MODEL_ID,
|
|
25
40
|
},
|
|
26
|
-
/**
|
|
41
|
+
/** @deprecated Use `BedrockModels.FAST` instead. */
|
|
27
42
|
BUDGET: {
|
|
28
43
|
provider: 'bedrock',
|
|
29
|
-
modelId:
|
|
44
|
+
modelId: FAST_MODEL_ID,
|
|
30
45
|
},
|
|
31
|
-
/**
|
|
46
|
+
/** @deprecated Use `BedrockModels.FAST` instead. */
|
|
32
47
|
MICRO: {
|
|
33
48
|
provider: 'bedrock',
|
|
34
|
-
modelId:
|
|
49
|
+
modelId: FAST_MODEL_ID,
|
|
35
50
|
},
|
|
36
51
|
} as const satisfies Record<string, ModelConfig>;
|
|
37
52
|
|
|
@@ -65,6 +80,7 @@ export const OllamaModels = {
|
|
|
65
80
|
apiKey: 'ollama',
|
|
66
81
|
},
|
|
67
82
|
/** Strong reasoning at moderate size. Currently: DeepSeek R1 14B (~9 GB, needs 16 GB VRAM). */
|
|
83
|
+
// TODO: DeepSeek R1 is strong at reasoning but weak at tool calling — swap to a more tool-capable model.
|
|
68
84
|
MEDIUM: {
|
|
69
85
|
provider: 'openai-api',
|
|
70
86
|
modelId: 'deepseek-r1:14b',
|
package/src/types.ts
CHANGED
|
@@ -48,9 +48,9 @@ export interface AgentConfig<TContext = DefaultToolContext> {
|
|
|
48
48
|
* The agent does inference + tools only — no conversation history.
|
|
49
49
|
*/
|
|
50
50
|
inferenceOnly?: boolean;
|
|
51
|
-
model
|
|
52
|
-
/** Model(s) for AWS deployment. Tries candidates in order; throws if all fail. */
|
|
53
|
-
deployed
|
|
51
|
+
model?: {
|
|
52
|
+
/** Model(s) for AWS deployment. Tries candidates in order; throws if all fail. Defaults to BedrockModels.BALANCED. */
|
|
53
|
+
deployed?: ModelConfig | ModelConfig[];
|
|
54
54
|
/** Model(s) for local development. Tries candidates in order; canned is implicit last fallback. */
|
|
55
55
|
local?: ModelConfig | ModelConfig[];
|
|
56
56
|
};
|
|
@@ -251,7 +251,7 @@ export interface ToolCallRecord {
|
|
|
251
251
|
|
|
252
252
|
export interface StreamOptions<TContext = DefaultToolContext> {
|
|
253
253
|
conversationId?: string;
|
|
254
|
-
/** Channel ID for Realtime delivery. Defaults to conversationId or a random UUID. */
|
|
254
|
+
/** Channel ID for Realtime delivery. Defaults to conversationId or a random UUID. Empty strings are treated as unset. */
|
|
255
255
|
channelId?: string;
|
|
256
256
|
/** User ID for conversation scoping. Defaults to 'anonymous'. */
|
|
257
257
|
userId?: string;
|
|
@@ -264,14 +264,30 @@ export interface StreamOptions<TContext = DefaultToolContext> {
|
|
|
264
264
|
context?: TContext;
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
/**
|
|
267
|
+
/**
|
|
268
|
+
* Returned by stream(). Provides the channelId and server-side convenience methods.
|
|
269
|
+
*
|
|
270
|
+
* Safe to return directly from API methods — `toJSON()` serializes to
|
|
271
|
+
* `{ channelId, channel: null }`. Only `channelId` is meaningful client-side;
|
|
272
|
+
* `channel` is explicitly `null` to signal the live handle is server-side only,
|
|
273
|
+
* and the `complete()` helper is dropped (functions don't serialize).
|
|
274
|
+
*/
|
|
268
275
|
export interface AgentStreamResult {
|
|
269
276
|
/** Realtime channel ID where chunks are published. */
|
|
270
277
|
channelId: string;
|
|
271
|
-
/**
|
|
278
|
+
/**
|
|
279
|
+
* Realtime channel handle (server-side only). Nulled by `toJSON()` — clients subscribe from `channelId` instead.
|
|
280
|
+
*
|
|
281
|
+
* @remarks
|
|
282
|
+
* Unlike `RealtimeChannel.toJSON()` which produces a hydratable descriptor, this is nulled
|
|
283
|
+
* because it's a `Promise` that can't round-trip. Clients reconstruct a subscribe-only
|
|
284
|
+
* channel from `channelId` via the `useChat` `subscribe` callback.
|
|
285
|
+
*/
|
|
272
286
|
channel: Promise<RealtimeChannel<AgentStreamChunk>>;
|
|
273
287
|
/** Wait for the complete response (server-side). Resolves when the done chunk arrives. */
|
|
274
288
|
complete: () => Promise<AgentStreamChunk>;
|
|
289
|
+
/** Only `{ channelId, channel: null }` is serialized when this object crosses the RPC boundary. */
|
|
290
|
+
toJSON(): { channelId: string; channel: null };
|
|
275
291
|
}
|
|
276
292
|
|
|
277
293
|
export interface AgentStreamChunk {
|
package/src/version.ts
CHANGED