@aws-blocks/bb-agent 0.1.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.
Files changed (75) hide show
  1. package/LICENSE +174 -0
  2. package/README.md +801 -0
  3. package/dist/agent.aws.d.ts +7 -0
  4. package/dist/agent.aws.d.ts.map +1 -0
  5. package/dist/agent.aws.js +9 -0
  6. package/dist/agent.d.ts +121 -0
  7. package/dist/agent.d.ts.map +1 -0
  8. package/dist/agent.js +588 -0
  9. package/dist/agent.mock.d.ts +7 -0
  10. package/dist/agent.mock.d.ts.map +1 -0
  11. package/dist/agent.mock.js +12 -0
  12. package/dist/errors.d.ts +39 -0
  13. package/dist/errors.d.ts.map +1 -0
  14. package/dist/errors.js +40 -0
  15. package/dist/file-bucket-snapshot-storage.d.ts +49 -0
  16. package/dist/file-bucket-snapshot-storage.d.ts.map +1 -0
  17. package/dist/file-bucket-snapshot-storage.js +84 -0
  18. package/dist/index.aws.d.ts +5 -0
  19. package/dist/index.aws.d.ts.map +1 -0
  20. package/dist/index.aws.js +5 -0
  21. package/dist/index.browser.d.ts +4 -0
  22. package/dist/index.browser.d.ts.map +1 -0
  23. package/dist/index.browser.js +8 -0
  24. package/dist/index.cdk.d.ts +15 -0
  25. package/dist/index.cdk.d.ts.map +1 -0
  26. package/dist/index.cdk.js +60 -0
  27. package/dist/index.hooks.d.ts +122 -0
  28. package/dist/index.hooks.d.ts.map +1 -0
  29. package/dist/index.hooks.js +179 -0
  30. package/dist/index.mock.d.ts +5 -0
  31. package/dist/index.mock.d.ts.map +1 -0
  32. package/dist/index.mock.js +5 -0
  33. package/dist/index.test.d.ts +2 -0
  34. package/dist/index.test.d.ts.map +1 -0
  35. package/dist/index.test.js +864 -0
  36. package/dist/model-factory.d.ts +26 -0
  37. package/dist/model-factory.d.ts.map +1 -0
  38. package/dist/model-factory.js +197 -0
  39. package/dist/models.d.ts +83 -0
  40. package/dist/models.d.ts.map +1 -0
  41. package/dist/models.js +84 -0
  42. package/dist/providers/canned.d.ts +32 -0
  43. package/dist/providers/canned.d.ts.map +1 -0
  44. package/dist/providers/canned.js +187 -0
  45. package/dist/providers/throwing.d.ts +10 -0
  46. package/dist/providers/throwing.d.ts.map +1 -0
  47. package/dist/providers/throwing.js +16 -0
  48. package/dist/schemas.d.ts +59 -0
  49. package/dist/schemas.d.ts.map +1 -0
  50. package/dist/schemas.js +36 -0
  51. package/dist/types.d.ts +295 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/types.js +3 -0
  54. package/dist/version.d.ts +3 -0
  55. package/dist/version.d.ts.map +1 -0
  56. package/dist/version.js +3 -0
  57. package/package.json +59 -0
  58. package/src/agent.aws.ts +13 -0
  59. package/src/agent.mock.ts +16 -0
  60. package/src/agent.ts +604 -0
  61. package/src/errors.ts +44 -0
  62. package/src/file-bucket-snapshot-storage.ts +85 -0
  63. package/src/index.aws.ts +7 -0
  64. package/src/index.browser.ts +10 -0
  65. package/src/index.cdk.ts +70 -0
  66. package/src/index.hooks.ts +256 -0
  67. package/src/index.mock.ts +7 -0
  68. package/src/index.test.ts +1010 -0
  69. package/src/model-factory.ts +228 -0
  70. package/src/models.ts +88 -0
  71. package/src/providers/canned.ts +205 -0
  72. package/src/providers/throwing.ts +19 -0
  73. package/src/schemas.ts +40 -0
  74. package/src/types.ts +311 -0
  75. package/src/version.ts +3 -0
package/dist/errors.js ADDED
@@ -0,0 +1,40 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Typed error constants for Agent BB. Use with `isBlocksError()` in catch blocks.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { isBlocksError } from '@aws-blocks/core';
9
+ * import { AgentErrors } from '@aws-blocks/bb-agent';
10
+ *
11
+ * try {
12
+ * await agent.getConversation(id);
13
+ * } catch (e) {
14
+ * if (isBlocksError(e, AgentErrors.PersistenceRequired)) {
15
+ * // agent is in inferenceOnly mode
16
+ * }
17
+ * }
18
+ * ```
19
+ */
20
+ export const AgentErrors = {
21
+ PersistenceRequired: 'PersistenceRequiredException',
22
+ InvalidModelConfig: 'InvalidModelConfigException',
23
+ ModelUnavailable: 'ModelUnavailableException',
24
+ BrowserNotSupported: 'BrowserNotSupportedException',
25
+ StreamFailed: 'StreamFailedException',
26
+ InterruptRequired: 'InterruptRequiredException',
27
+ };
28
+ export function blocksAgentError(name, message) {
29
+ const err = new Error(`${name}: ${message}`);
30
+ err.name = name;
31
+ return err;
32
+ }
33
+ export class InterruptError extends Error {
34
+ interrupts;
35
+ constructor(message, interrupts) {
36
+ super(`${AgentErrors.InterruptRequired}: ${message}`);
37
+ this.name = AgentErrors.InterruptRequired;
38
+ this.interrupts = interrupts;
39
+ }
40
+ }
@@ -0,0 +1,49 @@
1
+ import type { SnapshotStorage, SnapshotLocation, Snapshot, SnapshotManifest } from '@strands-agents/sdk';
2
+ import type { FileBucket } from '@aws-blocks/bb-file-bucket';
3
+ /**
4
+ * SnapshotStorage backed by FileBucket BB.
5
+ * Used locally — on AWS, Strands' native S3Storage talks to the same bucket directly.
6
+ *
7
+ * Mirrors the Strands S3Storage implementation exactly so that local and AWS
8
+ * produce identical key layouts. If S3Storage changes, update this to match.
9
+ *
10
+ * Key layout (same as S3Storage):
11
+ * <sessionId>/scopes/<scope>/<scopeId>/snapshots/snapshot_latest.json
12
+ * <sessionId>/scopes/<scope>/<scopeId>/snapshots/immutable_history/snapshot_<uuid>.json
13
+ * <sessionId>/scopes/<scope>/<scopeId>/snapshots/manifest.json
14
+ *
15
+ * @see https://strandsagents.com/docs/user-guide/concepts/agents/session-management/#s3sessionmanager--s3storage
16
+ * @see https://strandsagents.com/docs/user-guide/concepts/agents/session-management
17
+ */
18
+ export declare class FileBucketSnapshotStorage implements SnapshotStorage {
19
+ private bucket;
20
+ constructor(bucket: FileBucket);
21
+ /** Base path for a scope's snapshots folder. */
22
+ private basePath;
23
+ saveSnapshot(params: {
24
+ location: SnapshotLocation;
25
+ snapshotId: string;
26
+ isLatest: boolean;
27
+ snapshot: Snapshot;
28
+ }): Promise<void>;
29
+ loadSnapshot(params: {
30
+ location: SnapshotLocation;
31
+ snapshotId?: string;
32
+ }): Promise<Snapshot | null>;
33
+ listSnapshotIds(params: {
34
+ location: SnapshotLocation;
35
+ limit?: number;
36
+ startAfter?: string;
37
+ }): Promise<string[]>;
38
+ deleteSession(params: {
39
+ sessionId: string;
40
+ }): Promise<void>;
41
+ loadManifest(params: {
42
+ location: SnapshotLocation;
43
+ }): Promise<SnapshotManifest>;
44
+ saveManifest(params: {
45
+ location: SnapshotLocation;
46
+ manifest: SnapshotManifest;
47
+ }): Promise<void>;
48
+ }
49
+ //# sourceMappingURL=file-bucket-snapshot-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-bucket-snapshot-storage.d.ts","sourceRoot":"","sources":["../src/file-bucket-snapshot-storage.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACzG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAE7D;;;;;;;;;;;;;;GAcG;AACH,qBAAa,yBAA0B,YAAW,eAAe;IACpD,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,UAAU;IAEtC,gDAAgD;IAChD,OAAO,CAAC,QAAQ;IAIV,YAAY,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,gBAAgB,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAW9H,YAAY,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,gBAAgB,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IASnG,eAAe,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,gBAAgB,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAkB/G,aAAa,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ3D,YAAY,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,gBAAgB,CAAA;KAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAM/E,YAAY,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,gBAAgB,CAAC;QAAC,QAAQ,EAAE,gBAAgB,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAGrG"}
@@ -0,0 +1,84 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * SnapshotStorage backed by FileBucket BB.
5
+ * Used locally — on AWS, Strands' native S3Storage talks to the same bucket directly.
6
+ *
7
+ * Mirrors the Strands S3Storage implementation exactly so that local and AWS
8
+ * produce identical key layouts. If S3Storage changes, update this to match.
9
+ *
10
+ * Key layout (same as S3Storage):
11
+ * <sessionId>/scopes/<scope>/<scopeId>/snapshots/snapshot_latest.json
12
+ * <sessionId>/scopes/<scope>/<scopeId>/snapshots/immutable_history/snapshot_<uuid>.json
13
+ * <sessionId>/scopes/<scope>/<scopeId>/snapshots/manifest.json
14
+ *
15
+ * @see https://strandsagents.com/docs/user-guide/concepts/agents/session-management/#s3sessionmanager--s3storage
16
+ * @see https://strandsagents.com/docs/user-guide/concepts/agents/session-management
17
+ */
18
+ export class FileBucketSnapshotStorage {
19
+ bucket;
20
+ constructor(bucket) {
21
+ this.bucket = bucket;
22
+ }
23
+ /** Base path for a scope's snapshots folder. */
24
+ basePath(location) {
25
+ return `${location.sessionId}/scopes/${location.scope}/${location.scopeId}/snapshots`;
26
+ }
27
+ async saveSnapshot(params) {
28
+ const data = JSON.stringify(params.snapshot, null, 2);
29
+ if (params.isLatest) {
30
+ // Overwrite the mutable latest snapshot
31
+ await this.bucket.put(`${this.basePath(params.location)}/snapshot_latest.json`, data);
32
+ }
33
+ else {
34
+ // Append-only immutable checkpoint
35
+ await this.bucket.put(`${this.basePath(params.location)}/immutable_history/snapshot_${params.snapshotId}.json`, data);
36
+ }
37
+ }
38
+ async loadSnapshot(params) {
39
+ const path = params.snapshotId
40
+ ? `${this.basePath(params.location)}/immutable_history/snapshot_${params.snapshotId}.json`
41
+ : `${this.basePath(params.location)}/snapshot_latest.json`;
42
+ const file = await this.bucket.get(path);
43
+ if (!file)
44
+ return null;
45
+ return JSON.parse(file.body.toString());
46
+ }
47
+ async listSnapshotIds(params) {
48
+ const prefix = `${this.basePath(params.location)}/immutable_history/`;
49
+ const ids = [];
50
+ let pastCursor = !params.startAfter;
51
+ for await (const file of this.bucket.scan({ prefix })) {
52
+ const match = file.path.match(/snapshot_([\w-]+)\.json$/);
53
+ if (!match)
54
+ continue;
55
+ const id = match[1];
56
+ if (!pastCursor) {
57
+ if (id === params.startAfter)
58
+ pastCursor = true;
59
+ continue;
60
+ }
61
+ ids.push(id);
62
+ if (params.limit && ids.length >= params.limit)
63
+ break;
64
+ }
65
+ return ids;
66
+ }
67
+ async deleteSession(params) {
68
+ const paths = [];
69
+ for await (const file of this.bucket.scan({ prefix: `${params.sessionId}/` })) {
70
+ paths.push(file.path);
71
+ }
72
+ if (paths.length > 0)
73
+ await this.bucket.deleteBatch(paths);
74
+ }
75
+ async loadManifest(params) {
76
+ const file = await this.bucket.get(`${this.basePath(params.location)}/manifest.json`);
77
+ if (!file)
78
+ return { schemaVersion: '1.0', updatedAt: new Date().toISOString() };
79
+ return JSON.parse(file.body.toString());
80
+ }
81
+ async saveManifest(params) {
82
+ await this.bucket.put(`${this.basePath(params.location)}/manifest.json`, JSON.stringify(params.manifest, null, 2));
83
+ }
84
+ }
@@ -0,0 +1,5 @@
1
+ export { Agent } from './agent.aws.js';
2
+ export { AgentErrors, InterruptError } from './errors.js';
3
+ export { BedrockModels, OllamaModels } from './models.js';
4
+ export type { AgentConfig, AgentResult, AgentStreamChunk, AgentStreamResult, ToolDefinition, AgentTool, ToolFactory, ToolsConfig, ToolHandlerArgs, DefaultToolContext, InterruptResponse, ToolCallRecord, ModelConfig, StreamOptions, Message, Conversation, JSONValue, TokenUsage } from './types.js';
5
+ //# sourceMappingURL=index.aws.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.aws.d.ts","sourceRoot":"","sources":["../src/index.aws.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC1D,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,cAAc,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,cAAc,EAAE,WAAW,EAAE,aAAa,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,5 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ export { Agent } from './agent.aws.js';
4
+ export { AgentErrors, InterruptError } from './errors.js';
5
+ export { BedrockModels, OllamaModels } from './models.js';
@@ -0,0 +1,4 @@
1
+ export declare class Agent {
2
+ constructor(..._args: any[]);
3
+ }
4
+ //# sourceMappingURL=index.browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.browser.d.ts","sourceRoot":"","sources":["../src/index.browser.ts"],"names":[],"mappings":"AAKA,qBAAa,KAAK;gBACJ,GAAG,KAAK,EAAE,GAAG,EAAE;CAG5B"}
@@ -0,0 +1,8 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { AgentErrors, blocksAgentError } from './errors.js';
4
+ export class Agent {
5
+ constructor(..._args) {
6
+ throw blocksAgentError(AgentErrors.BrowserNotSupported, 'Agent can only be instantiated on the server.');
7
+ }
8
+ }
@@ -0,0 +1,15 @@
1
+ import { Scope } from '@aws-blocks/core/cdk';
2
+ import type { ScopeParent } from '@aws-blocks/core';
3
+ export { AgentErrors } from './errors.js';
4
+ export { BedrockModels, OllamaModels } from './models.js';
5
+ export declare class Agent extends Scope {
6
+ /**
7
+ * CDK layer for the Agent BB.
8
+ * Mirrors the runtime's BB creation so CDK discovers and provisions all resources.
9
+ *
10
+ * TODO: scope Bedrock IAM grant to specific modelId from config
11
+ * TODO: guardrails CDK provisioning
12
+ */
13
+ constructor(scope: ScopeParent, id: string, config?: any);
14
+ }
15
+ //# sourceMappingURL=index.cdk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cdk.d.ts","sourceRoot":"","sources":["../src/index.cdk.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC7C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAQpD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAO1D,qBAAa,KAAM,SAAQ,KAAK;IAC/B;;;;;;OAMG;gBACS,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG;CAwCxD"}
@@ -0,0 +1,60 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { PolicyStatement } from 'aws-cdk-lib/aws-iam';
4
+ import { Scope } from '@aws-blocks/core/cdk';
5
+ import { DistributedTable } from '@aws-blocks/bb-distributed-table';
6
+ import { Realtime } from '@aws-blocks/bb-realtime';
7
+ import { AsyncJob } from '@aws-blocks/bb-async-job';
8
+ import { FileBucket } from '@aws-blocks/bb-file-bucket';
9
+ import { messageSchema, conversationSchema, agentStreamChunkSchema } from './schemas.js';
10
+ import { z } from 'zod';
11
+ export { AgentErrors } from './errors.js';
12
+ export { BedrockModels, OllamaModels } from './models.js';
13
+ const jobPayloadSchema = z.object({
14
+ message: z.string(),
15
+ conversationId: z.string().optional(),
16
+ });
17
+ export class Agent extends Scope {
18
+ /**
19
+ * CDK layer for the Agent BB.
20
+ * Mirrors the runtime's BB creation so CDK discovers and provisions all resources.
21
+ *
22
+ * TODO: scope Bedrock IAM grant to specific modelId from config
23
+ * TODO: guardrails CDK provisioning
24
+ */
25
+ constructor(scope, id, config) {
26
+ super(id, { parent: scope });
27
+ this.handler.addToRolePolicy(new PolicyStatement({
28
+ actions: ['bedrock:InvokeModel', 'bedrock:InvokeModelWithResponseStream', 'bedrock:GetFoundationModel', 'bedrock:ListFoundationModels', 'bedrock:GetInferenceProfile'],
29
+ resources: [
30
+ 'arn:aws:bedrock:*::foundation-model/*',
31
+ 'arn:aws:bedrock:*:*:inference-profile/*',
32
+ ],
33
+ }));
34
+ // Propagate `removalPolicy` to the sessions bucket so customers can
35
+ // opt sandbox stacks into clean teardown. Without it, CDK's RETAIN
36
+ // default applies (production-safe) and `cdk destroy` will fail on
37
+ // a non-empty bucket — same pattern as FileBucket / KnowledgeBase.
38
+ // ID shortened to keep S3 bucket names within the 63-char limit
39
+ new FileBucket(this, 'sn', { removalPolicy: config?.removalPolicy });
40
+ if (!config?.inferenceOnly) {
41
+ new DistributedTable(this, 'convos', {
42
+ schema: conversationSchema,
43
+ key: { partitionKey: 'userId', sortKey: 'conversationId' },
44
+ });
45
+ new DistributedTable(this, 'messages', {
46
+ schema: messageSchema,
47
+ key: { partitionKey: 'conversationId', sortKey: 'messageId' },
48
+ });
49
+ }
50
+ new Realtime(this, 'rt', {
51
+ namespaces: {
52
+ chunks: { schema: agentStreamChunkSchema },
53
+ },
54
+ });
55
+ new AsyncJob(this, 'job', {
56
+ schema: jobPayloadSchema,
57
+ handler: async () => { },
58
+ });
59
+ }
60
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Client hooks for Agent BB.
3
+ *
4
+ * useChat() provides state management for agent conversations.
5
+ * Works with any framework — not React-specific (no JSX, no React imports).
6
+ *
7
+ * Flow:
8
+ * 1. Subscribe to Realtime channel + await established
9
+ * 2. Load existing history from DB
10
+ * 3. Show history — any in-flight chunks are caught by the subscription
11
+ * 4. User sends message — chunks arrive via the already-open subscription
12
+ */
13
+ import type { AgentStreamChunk } from './types.js';
14
+ export type { AgentStreamChunk } from './types.js';
15
+ /** A message in the conversation (for UI rendering). */
16
+ export interface ChatMessage {
17
+ id: string;
18
+ role: 'user' | 'assistant' | 'approval';
19
+ content: string;
20
+ metadata?: Record<string, any>;
21
+ }
22
+ /** Options for creating a chat instance. */
23
+ export interface UseChatOptions {
24
+ api: {
25
+ sendMessage(conversationId: string, message: string, channelId: string): Promise<void>;
26
+ createConversation(): Promise<{
27
+ conversationId: string;
28
+ }>;
29
+ getConversation(id: string): Promise<{
30
+ messages: {
31
+ role: string;
32
+ content: string;
33
+ metadata?: Record<string, any>;
34
+ }[];
35
+ }>;
36
+ resume?(channelId: string, responses: Array<{
37
+ interruptId: string;
38
+ approved: boolean;
39
+ trust?: boolean;
40
+ toolName?: string;
41
+ input?: any;
42
+ }>, conversationId?: string): Promise<void>;
43
+ getPendingInterrupts?(conversationId: string): Promise<{
44
+ interrupts: Array<{
45
+ id: string;
46
+ name: string;
47
+ reason?: any;
48
+ }>;
49
+ }>;
50
+ };
51
+ /**
52
+ * Subscribe to a Realtime channel. Must return an object with:
53
+ * - unsubscribe(): stop receiving messages
54
+ * - established: Promise that resolves when the WS subscription is confirmed
55
+ */
56
+ subscribe: (channelId: string, handler: (chunk: AgentStreamChunk) => void) => Promise<{
57
+ unsubscribe(): void;
58
+ established: Promise<void>;
59
+ }>;
60
+ /** Called whenever the message list changes. */
61
+ onMessagesChange?: (messages: ChatMessage[]) => void;
62
+ /** Called whenever loading state changes. */
63
+ onLoadingChange?: (isLoading: boolean) => void;
64
+ /** Called on each streaming chunk. */
65
+ onChunk?: (chunk: AgentStreamChunk) => void;
66
+ /** Called when the agent encounters an error. */
67
+ onError?: (error: string) => void;
68
+ /** Called when the agent needs human approval before continuing. */
69
+ onInterrupt?: (interrupts: Array<{
70
+ id: string;
71
+ name: string;
72
+ reason?: any;
73
+ }>) => void;
74
+ }
75
+ /** Returned by useChat(). */
76
+ export interface ChatInstance {
77
+ /** Send a message. Creates a conversation and subscribes if needed. */
78
+ sendMessage(text: string): Promise<void>;
79
+ /** Respond to an interrupt (tool approval). Resumes the agent. */
80
+ respondToInterrupt(responses: Array<{
81
+ interruptId: string;
82
+ approved: boolean;
83
+ trust?: boolean;
84
+ toolName?: string;
85
+ input?: any;
86
+ }>): Promise<void>;
87
+ /** Current messages. */
88
+ getMessages(): ChatMessage[];
89
+ /** Whether the agent is currently responding. */
90
+ isLoading(): boolean;
91
+ /** Current conversation ID (null until first message). */
92
+ getConversationId(): string | null;
93
+ /** Open a conversation: subscribe to Realtime, then load history. */
94
+ loadConversation(conversationId: string): Promise<void>;
95
+ /** Clean up the active subscription. */
96
+ destroy(): void;
97
+ }
98
+ /**
99
+ * Create a chat instance for managing agent conversations.
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * const chat = useChat({
104
+ * api: {
105
+ * sendMessage: (convId, msg, chId) => api.agentStream(msg, convId, chId),
106
+ * createConversation: () => api.agentCreateConversationId(),
107
+ * getConversation: (id) => api.agentGetConversation(id),
108
+ * },
109
+ * subscribe: async (channelId, handler) => {
110
+ * const result = await api.agentGetChannel(channelId);
111
+ * return result.channel.subscribe(handler);
112
+ * },
113
+ * onMessagesChange: (msgs) => renderMessages(msgs),
114
+ * onLoadingChange: (loading) => updateSpinner(loading),
115
+ * });
116
+ *
117
+ * await chat.loadConversation('conv-123');
118
+ * await chat.sendMessage('Hello!');
119
+ * ```
120
+ */
121
+ export declare function useChat(options: UseChatOptions): ChatInstance;
122
+ //# sourceMappingURL=index.hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.hooks.d.ts","sourceRoot":"","sources":["../src/index.hooks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,179 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ let messageCounter = 0;
4
+ function nextId() {
5
+ return `msg-${++messageCounter}-${Date.now()}`;
6
+ }
7
+ /**
8
+ * Create a chat instance for managing agent conversations.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * const chat = useChat({
13
+ * api: {
14
+ * sendMessage: (convId, msg, chId) => api.agentStream(msg, convId, chId),
15
+ * createConversation: () => api.agentCreateConversationId(),
16
+ * getConversation: (id) => api.agentGetConversation(id),
17
+ * },
18
+ * subscribe: async (channelId, handler) => {
19
+ * const result = await api.agentGetChannel(channelId);
20
+ * return result.channel.subscribe(handler);
21
+ * },
22
+ * onMessagesChange: (msgs) => renderMessages(msgs),
23
+ * onLoadingChange: (loading) => updateSpinner(loading),
24
+ * });
25
+ *
26
+ * await chat.loadConversation('conv-123');
27
+ * await chat.sendMessage('Hello!');
28
+ * ```
29
+ */
30
+ export function useChat(options) {
31
+ let messages = [];
32
+ let loading = false;
33
+ let conversationId = null;
34
+ let activeSub = null;
35
+ let assistantId = null;
36
+ let assistantText = '';
37
+ /** Handle a chunk from the Realtime subscription. */
38
+ function handleChunk(chunk) {
39
+ options.onChunk?.(chunk);
40
+ if (chunk.type === 'text-delta' && chunk.text && assistantId) {
41
+ assistantText += chunk.text;
42
+ messages = messages.map(m => m.id === assistantId ? { ...m, content: assistantText } : m);
43
+ options.onMessagesChange?.(messages);
44
+ }
45
+ if (chunk.type === 'done') {
46
+ if (chunk.text && assistantId) {
47
+ messages = messages.map(m => m.id === assistantId ? { ...m, content: chunk.text } : m);
48
+ options.onMessagesChange?.(messages);
49
+ }
50
+ loading = false;
51
+ options.onLoadingChange?.(loading);
52
+ }
53
+ if (chunk.type === 'error') {
54
+ loading = false;
55
+ options.onLoadingChange?.(loading);
56
+ options.onError?.(chunk.error ?? 'Unknown error');
57
+ }
58
+ if (chunk.type === 'interrupt' && chunk.interrupts) {
59
+ // Remove empty assistant placeholder (no text was generated before interrupt)
60
+ if (assistantId) {
61
+ const assistant = messages.find(m => m.id === assistantId);
62
+ if (assistant && !assistant.content) {
63
+ messages = messages.filter(m => m.id !== assistantId);
64
+ options.onMessagesChange?.(messages);
65
+ }
66
+ }
67
+ assistantId = null;
68
+ loading = false;
69
+ options.onLoadingChange?.(loading);
70
+ options.onInterrupt?.(chunk.interrupts);
71
+ }
72
+ }
73
+ /** Subscribe to a conversation's Realtime channel and wait for WS confirmation. Retries with fresh token on auth failure. */
74
+ async function ensureSubscribed(channelId) {
75
+ if (activeSub) {
76
+ activeSub.unsubscribe();
77
+ activeSub = null;
78
+ }
79
+ const sub = await options.subscribe(channelId, handleChunk);
80
+ try {
81
+ await sub.established;
82
+ }
83
+ catch (err) {
84
+ console.warn('Subscription failed, retrying with fresh token:', err);
85
+ sub.unsubscribe();
86
+ const retrySub = await options.subscribe(channelId, handleChunk);
87
+ await retrySub.established;
88
+ activeSub = retrySub;
89
+ return;
90
+ }
91
+ activeSub = sub;
92
+ }
93
+ return {
94
+ async sendMessage(text) {
95
+ if (loading)
96
+ return;
97
+ // Create conversation + subscribe on first message
98
+ if (!conversationId) {
99
+ const result = await options.api.createConversation();
100
+ conversationId = result.conversationId;
101
+ await ensureSubscribed(conversationId);
102
+ }
103
+ // Subscribe if not already (e.g., sendMessage without loadConversation)
104
+ if (!activeSub) {
105
+ await ensureSubscribed(conversationId);
106
+ }
107
+ // Add user message + assistant placeholder
108
+ const userMsg = { id: nextId(), role: 'user', content: text };
109
+ const aMsg = { id: nextId(), role: 'assistant', content: '' };
110
+ assistantId = aMsg.id;
111
+ assistantText = '';
112
+ messages = [...messages, userMsg, aMsg];
113
+ options.onMessagesChange?.(messages);
114
+ loading = true;
115
+ options.onLoadingChange?.(loading);
116
+ // Submit — chunks arrive via the already-open subscription
117
+ await options.api.sendMessage(conversationId, text, conversationId);
118
+ },
119
+ async respondToInterrupt(responses) {
120
+ if (loading)
121
+ return;
122
+ if (!conversationId)
123
+ throw new Error('No active conversation');
124
+ // Add approval messages to chat immediately
125
+ for (const r of responses) {
126
+ messages = [...messages, { id: nextId(), role: 'approval', content: r.approved ? 'Approved' : 'Denied', metadata: { approved: r.approved, trust: r.trust, toolName: r.toolName, input: r.input } }];
127
+ }
128
+ // Reuse existing empty assistant placeholder or create one
129
+ const existingEmpty = messages.find(m => m.role === 'assistant' && !m.content);
130
+ if (existingEmpty) {
131
+ assistantId = existingEmpty.id;
132
+ }
133
+ else {
134
+ const aMsg = { id: nextId(), role: 'assistant', content: '' };
135
+ assistantId = aMsg.id;
136
+ messages = [...messages, aMsg];
137
+ }
138
+ assistantText = '';
139
+ options.onMessagesChange?.(messages);
140
+ loading = true;
141
+ options.onLoadingChange?.(loading);
142
+ if (!options.api.resume)
143
+ throw new Error('respondToInterrupt requires api.resume to be configured');
144
+ await options.api.resume(conversationId, responses, conversationId);
145
+ },
146
+ getMessages() { return messages; },
147
+ isLoading() { return loading; },
148
+ getConversationId() { return conversationId; },
149
+ async loadConversation(id) {
150
+ conversationId = id;
151
+ // 1. Subscribe FIRST — catch any in-flight chunks
152
+ await ensureSubscribed(id);
153
+ // 2. THEN load history from DB
154
+ // TODO: buffer chunks received between subscribe and history load, then deduplicate/merge
155
+ const { messages: history } = await options.api.getConversation(id);
156
+ messages = history
157
+ .filter(m => m.role === 'user' || m.role === 'assistant' || m.role === 'approval')
158
+ .map(m => ({
159
+ id: nextId(),
160
+ role: m.role,
161
+ content: m.content,
162
+ metadata: m.metadata,
163
+ }));
164
+ options.onMessagesChange?.(messages);
165
+ // Check for pending interrupts (e.g., user left mid-approval)
166
+ if (options.api.getPendingInterrupts) {
167
+ const { interrupts } = await options.api.getPendingInterrupts(id);
168
+ if (interrupts.length)
169
+ options.onInterrupt?.(interrupts);
170
+ }
171
+ },
172
+ destroy() {
173
+ if (activeSub) {
174
+ activeSub.unsubscribe();
175
+ activeSub = null;
176
+ }
177
+ },
178
+ };
179
+ }
@@ -0,0 +1,5 @@
1
+ export { Agent } from './agent.mock.js';
2
+ export { AgentErrors, InterruptError } from './errors.js';
3
+ export { BedrockModels, OllamaModels } from './models.js';
4
+ export type { AgentConfig, AgentResult, AgentStreamChunk, AgentStreamResult, ToolDefinition, AgentTool, ToolFactory, ToolsConfig, ToolHandlerArgs, DefaultToolContext, InterruptResponse, ToolCallRecord, ModelConfig, StreamOptions, Message, Conversation, JSONValue, TokenUsage } from './types.js';
5
+ //# sourceMappingURL=index.mock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mock.d.ts","sourceRoot":"","sources":["../src/index.mock.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC1D,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,cAAc,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,cAAc,EAAE,WAAW,EAAE,aAAa,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,5 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ export { Agent } from './agent.mock.js';
4
+ export { AgentErrors, InterruptError } from './errors.js';
5
+ export { BedrockModels, OllamaModels } from './models.js';
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":""}