@ably/ai-transport 0.0.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/LICENSE +176 -0
- package/README.md +426 -0
- package/dist/ably-ai-transport.js +1388 -0
- package/dist/ably-ai-transport.js.map +1 -0
- package/dist/ably-ai-transport.umd.cjs +2 -0
- package/dist/ably-ai-transport.umd.cjs.map +1 -0
- package/dist/constants.d.ts +50 -0
- package/dist/core/codec/decoder.d.ts +62 -0
- package/dist/core/codec/encoder.d.ts +56 -0
- package/dist/core/codec/index.d.ts +8 -0
- package/dist/core/codec/lifecycle-tracker.d.ts +74 -0
- package/dist/core/codec/types.d.ts +188 -0
- package/dist/core/transport/client-transport.d.ts +10 -0
- package/dist/core/transport/conversation-tree.d.ts +9 -0
- package/dist/core/transport/decode-history.d.ts +41 -0
- package/dist/core/transport/headers.d.ts +26 -0
- package/dist/core/transport/index.d.ts +4 -0
- package/dist/core/transport/pipe-stream.d.ts +16 -0
- package/dist/core/transport/server-transport.d.ts +7 -0
- package/dist/core/transport/stream-router.d.ts +19 -0
- package/dist/core/transport/turn-manager.d.ts +34 -0
- package/dist/core/transport/types.d.ts +407 -0
- package/dist/errors.d.ts +46 -0
- package/dist/event-emitter.d.ts +65 -0
- package/dist/index.d.ts +11 -0
- package/dist/logger.d.ts +103 -0
- package/dist/react/ably-ai-transport-react.js +823 -0
- package/dist/react/ably-ai-transport-react.js.map +1 -0
- package/dist/react/ably-ai-transport-react.umd.cjs +2 -0
- package/dist/react/ably-ai-transport-react.umd.cjs.map +1 -0
- package/dist/react/index.d.ts +11 -0
- package/dist/react/use-ably-messages.d.ts +18 -0
- package/dist/react/use-active-turns.d.ts +8 -0
- package/dist/react/use-client-transport.d.ts +7 -0
- package/dist/react/use-conversation-tree.d.ts +20 -0
- package/dist/react/use-edit.d.ts +7 -0
- package/dist/react/use-history.d.ts +19 -0
- package/dist/react/use-messages.d.ts +7 -0
- package/dist/react/use-regenerate.d.ts +7 -0
- package/dist/react/use-send.d.ts +7 -0
- package/dist/utils.d.ts +127 -0
- package/dist/vercel/ably-ai-transport-vercel.js +2331 -0
- package/dist/vercel/ably-ai-transport-vercel.js.map +1 -0
- package/dist/vercel/ably-ai-transport-vercel.umd.cjs +2 -0
- package/dist/vercel/ably-ai-transport-vercel.umd.cjs.map +1 -0
- package/dist/vercel/codec/accumulator.d.ts +21 -0
- package/dist/vercel/codec/decoder.d.ts +22 -0
- package/dist/vercel/codec/encoder.d.ts +41 -0
- package/dist/vercel/codec/index.d.ts +22 -0
- package/dist/vercel/index.d.ts +3 -0
- package/dist/vercel/react/ably-ai-transport-vercel-react.js +2082 -0
- package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -0
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +2 -0
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -0
- package/dist/vercel/react/index.d.ts +3 -0
- package/dist/vercel/react/use-chat-transport.d.ts +29 -0
- package/dist/vercel/react/use-message-sync.d.ts +19 -0
- package/dist/vercel/transport/chat-transport.d.ts +118 -0
- package/dist/vercel/transport/index.d.ts +36 -0
- package/package.json +123 -0
- package/react/README.md +3 -0
- package/react/index.d.ts +1 -0
- package/react/index.js +1 -0
- package/react/index.umd.cjs +1 -0
- package/src/constants.ts +98 -0
- package/src/core/codec/decoder.ts +402 -0
- package/src/core/codec/encoder.ts +470 -0
- package/src/core/codec/index.ts +28 -0
- package/src/core/codec/lifecycle-tracker.ts +140 -0
- package/src/core/codec/types.ts +249 -0
- package/src/core/transport/client-transport.ts +959 -0
- package/src/core/transport/conversation-tree.ts +434 -0
- package/src/core/transport/decode-history.ts +337 -0
- package/src/core/transport/headers.ts +46 -0
- package/src/core/transport/index.ts +34 -0
- package/src/core/transport/pipe-stream.ts +95 -0
- package/src/core/transport/server-transport.ts +458 -0
- package/src/core/transport/stream-router.ts +118 -0
- package/src/core/transport/turn-manager.ts +147 -0
- package/src/core/transport/types.ts +533 -0
- package/src/errors.ts +58 -0
- package/src/event-emitter.ts +103 -0
- package/src/index.ts +89 -0
- package/src/logger.ts +241 -0
- package/src/react/index.ts +11 -0
- package/src/react/use-ably-messages.ts +37 -0
- package/src/react/use-active-turns.ts +61 -0
- package/src/react/use-client-transport.ts +37 -0
- package/src/react/use-conversation-tree.ts +71 -0
- package/src/react/use-edit.ts +24 -0
- package/src/react/use-history.ts +111 -0
- package/src/react/use-messages.ts +32 -0
- package/src/react/use-regenerate.ts +24 -0
- package/src/react/use-send.ts +25 -0
- package/src/react/vite.config.ts +32 -0
- package/src/tsconfig.json +25 -0
- package/src/utils.ts +230 -0
- package/src/vercel/codec/accumulator.ts +603 -0
- package/src/vercel/codec/decoder.ts +615 -0
- package/src/vercel/codec/encoder.ts +396 -0
- package/src/vercel/codec/index.ts +37 -0
- package/src/vercel/index.ts +12 -0
- package/src/vercel/react/index.ts +4 -0
- package/src/vercel/react/use-chat-transport.ts +60 -0
- package/src/vercel/react/use-message-sync.ts +34 -0
- package/src/vercel/react/vite.config.ts +33 -0
- package/src/vercel/transport/chat-transport.ts +278 -0
- package/src/vercel/transport/index.ts +56 -0
- package/src/vercel/vite.config.ts +33 -0
- package/src/vite.config.ts +31 -0
- package/vercel/README.md +3 -0
- package/vercel/index.d.ts +1 -0
- package/vercel/index.js +1 -0
- package/vercel/index.umd.cjs +1 -0
- package/vercel/react/README.md +3 -0
- package/vercel/react/index.d.ts +1 -0
- package/vercel/react/index.js +1 -0
- package/vercel/react/index.umd.cjs +1 -0
package/src/errors.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as Ably from 'ably';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Error codes for the AI Transport SDK.
|
|
5
|
+
*/
|
|
6
|
+
export enum ErrorCode {
|
|
7
|
+
/**
|
|
8
|
+
* The request was invalid.
|
|
9
|
+
*/
|
|
10
|
+
BadRequest = 40000,
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Invalid argument provided.
|
|
14
|
+
*/
|
|
15
|
+
InvalidArgument = 40003,
|
|
16
|
+
|
|
17
|
+
// 104000 - 104999 are reserved for AI Transport SDK errors
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Encoder recovery failed after flush — one or more updateMessage calls
|
|
21
|
+
* could not recover a failed append pipeline.
|
|
22
|
+
*/
|
|
23
|
+
EncoderRecoveryFailed = 104000,
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A transport-level channel subscription callback threw unexpectedly.
|
|
27
|
+
*/
|
|
28
|
+
TransportSubscriptionError = 104001,
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Cancel listener or onCancel hook threw while processing a cancel message.
|
|
32
|
+
*/
|
|
33
|
+
CancelListenerError = 104002,
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A turn lifecycle event (turn-start or turn-end) failed to publish.
|
|
37
|
+
*/
|
|
38
|
+
TurnLifecycleError = 104003,
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* An operation was attempted on a transport that has already been closed.
|
|
42
|
+
*/
|
|
43
|
+
TransportClosed = 104004,
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The HTTP POST to the server endpoint failed (network error or non-2xx response).
|
|
47
|
+
*/
|
|
48
|
+
TransportSendFailed = 104005,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Returns true if the {@link Ably.ErrorInfo} code matches the provided ErrorCode value.
|
|
53
|
+
* @param errorInfo The error info to check.
|
|
54
|
+
* @param error The error code to compare against.
|
|
55
|
+
* @returns true if the error code matches, false otherwise.
|
|
56
|
+
*/
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
|
|
58
|
+
export const errorInfoIs = (errorInfo: Ably.ErrorInfo, error: ErrorCode): boolean => errorInfo.code === error;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-safe EventEmitter wrapping Ably's internal EventEmitter.
|
|
3
|
+
*
|
|
4
|
+
* Takes a single `EventsMap` type parameter — an interface mapping event names
|
|
5
|
+
* to payload types — rather than Ably's three type parameters. Adapted from
|
|
6
|
+
* the ably-chat-js SDK.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* interface MyEvents {
|
|
10
|
+
* reaction: { emoji: string };
|
|
11
|
+
* status: { online: boolean };
|
|
12
|
+
* }
|
|
13
|
+
*
|
|
14
|
+
* const emitter = new EventEmitter<MyEvents>(logger);
|
|
15
|
+
* emitter.on('reaction', (event) => console.log(event.emoji));
|
|
16
|
+
* emitter.emit('reaction', { emoji: '👍' });
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import * as Ably from 'ably';
|
|
21
|
+
|
|
22
|
+
import type { Logger } from './logger.js';
|
|
23
|
+
|
|
24
|
+
/** Callback receiving a union of all possible event payloads. */
|
|
25
|
+
type Callback<EventsMap> = (arg: EventsMap[keyof EventsMap]) => void;
|
|
26
|
+
|
|
27
|
+
/** Callback receiving the payload for a single event type. */
|
|
28
|
+
type CallbackSingle<K> = (arg: K) => void;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Type-safe interface for the Ably EventEmitter, parameterized by an EventsMap
|
|
32
|
+
* that maps event names to their payload types.
|
|
33
|
+
*/
|
|
34
|
+
interface InterfaceEventEmitter<EventsMap> extends Ably.EventEmitter<Callback<EventsMap>, void, keyof EventsMap> {
|
|
35
|
+
/** Emit an event with a type-safe payload. Payload is optional for `undefined`-typed events. */
|
|
36
|
+
emit<K extends keyof EventsMap>(
|
|
37
|
+
event: K,
|
|
38
|
+
...args: EventsMap[K] extends undefined ? [EventsMap[K]?] : [EventsMap[K]]
|
|
39
|
+
): void;
|
|
40
|
+
|
|
41
|
+
/** Subscribe to a single event with a typed callback. */
|
|
42
|
+
on<K extends keyof EventsMap>(event: K, callback: CallbackSingle<EventsMap[K]>): void;
|
|
43
|
+
/** Subscribe to two events with a union-typed callback. */
|
|
44
|
+
on<K1 extends keyof EventsMap, K2 extends keyof EventsMap>(
|
|
45
|
+
events: [K1, K2],
|
|
46
|
+
callback: CallbackSingle<EventsMap[K1] | EventsMap[K2]>,
|
|
47
|
+
): void;
|
|
48
|
+
/** Subscribe to three events with a union-typed callback. */
|
|
49
|
+
on<K1 extends keyof EventsMap, K2 extends keyof EventsMap, K3 extends keyof EventsMap>(
|
|
50
|
+
events: [K1, K2, K3],
|
|
51
|
+
callback: CallbackSingle<EventsMap[K1] | EventsMap[K2] | EventsMap[K3]>,
|
|
52
|
+
): void;
|
|
53
|
+
/** Subscribe to an array of events. */
|
|
54
|
+
on(events: (keyof EventsMap)[], callback: Callback<EventsMap>): void;
|
|
55
|
+
/** Subscribe to all events. */
|
|
56
|
+
on(callback: Callback<EventsMap>): void;
|
|
57
|
+
|
|
58
|
+
/** Unsubscribe a callback from a specific event. */
|
|
59
|
+
off<K extends keyof EventsMap>(event: K, listener: CallbackSingle<EventsMap[K]>): void;
|
|
60
|
+
/** Unsubscribe a callback from all events, or remove all listeners if no callback provided. */
|
|
61
|
+
off(listener?: Callback<EventsMap>): void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Bridge from our {@link Logger} to the Ably EventEmitter's internal logger
|
|
66
|
+
* contract. Ably's EventEmitter calls `logger.logAction(level, action, message)`
|
|
67
|
+
* when a listener throws — we route that to our Logger's `error` method.
|
|
68
|
+
* @param logger - The application logger to delegate to.
|
|
69
|
+
* @returns An object satisfying the Ably EventEmitter's logger interface.
|
|
70
|
+
*/
|
|
71
|
+
const toAblyLogger = (logger: Logger): unknown => ({
|
|
72
|
+
logAction: (_level: number, action: string, message?: string) => {
|
|
73
|
+
logger.error(action, { detail: message });
|
|
74
|
+
},
|
|
75
|
+
shouldLog: () => true,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// CAST: Access Ably's internal EventEmitter constructor. Not publicly exported
|
|
79
|
+
// but available to other Ably SDKs. The logger parameter ensures listener
|
|
80
|
+
// exceptions are caught and logged rather than crashing.
|
|
81
|
+
const InternalEventEmitter: new <EventsMap>(logger: unknown) => InterfaceEventEmitter<EventsMap> = (
|
|
82
|
+
Ably.Realtime as unknown as { EventEmitter: new <EventsMap>(logger: unknown) => InterfaceEventEmitter<EventsMap> }
|
|
83
|
+
).EventEmitter;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Type-safe EventEmitter based on Ably's internal EventEmitter.
|
|
87
|
+
*
|
|
88
|
+
* Provides the same semantics as {@link Ably.EventEmitter} (error isolation
|
|
89
|
+
* between listeners, synchronous dispatch) but with a single `EventsMap` type
|
|
90
|
+
* parameter for ergonomic type safety.
|
|
91
|
+
*
|
|
92
|
+
* Requires a {@link Logger} so that listener exceptions are routed through
|
|
93
|
+
* the application's logging infrastructure rather than silently swallowed.
|
|
94
|
+
*/
|
|
95
|
+
export class EventEmitter<EventsMap> extends InternalEventEmitter<EventsMap> {
|
|
96
|
+
/**
|
|
97
|
+
* Create a new EventEmitter.
|
|
98
|
+
* @param logger - Application logger. Listener exceptions are logged at error level.
|
|
99
|
+
*/
|
|
100
|
+
constructor(logger: Logger) {
|
|
101
|
+
super(toAblyLogger(logger));
|
|
102
|
+
}
|
|
103
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Core transport
|
|
2
|
+
export type {
|
|
3
|
+
ActiveTurn,
|
|
4
|
+
AddMessageOptions,
|
|
5
|
+
AddMessagesResult,
|
|
6
|
+
CancelFilter,
|
|
7
|
+
CancelRequest,
|
|
8
|
+
ClientTransport,
|
|
9
|
+
ClientTransportOptions,
|
|
10
|
+
CloseOptions,
|
|
11
|
+
ConversationNode,
|
|
12
|
+
ConversationTree,
|
|
13
|
+
LoadHistoryOptions,
|
|
14
|
+
MessageWithHeaders,
|
|
15
|
+
NewTurnOptions,
|
|
16
|
+
PaginatedMessages,
|
|
17
|
+
SendOptions,
|
|
18
|
+
ServerTransport,
|
|
19
|
+
ServerTransportOptions,
|
|
20
|
+
StreamResponseOptions,
|
|
21
|
+
StreamResult,
|
|
22
|
+
Turn,
|
|
23
|
+
TurnEndReason,
|
|
24
|
+
TurnLifecycleEvent,
|
|
25
|
+
} from './core/transport/index.js';
|
|
26
|
+
export { buildTransportHeaders, createClientTransport, createServerTransport } from './core/transport/index.js';
|
|
27
|
+
|
|
28
|
+
// Core codec
|
|
29
|
+
export type {
|
|
30
|
+
ChannelWriter,
|
|
31
|
+
Codec,
|
|
32
|
+
DecoderCore,
|
|
33
|
+
DecoderCoreHooks,
|
|
34
|
+
DecoderCoreOptions,
|
|
35
|
+
DecoderOutput,
|
|
36
|
+
DiscreteEncoder,
|
|
37
|
+
EncoderCore,
|
|
38
|
+
EncoderCoreOptions,
|
|
39
|
+
EncoderOptions,
|
|
40
|
+
Extras,
|
|
41
|
+
LifecycleTracker,
|
|
42
|
+
MessageAccumulator,
|
|
43
|
+
MessagePayload,
|
|
44
|
+
PhaseConfig,
|
|
45
|
+
StreamDecoder,
|
|
46
|
+
StreamEncoder,
|
|
47
|
+
StreamPayload,
|
|
48
|
+
StreamTrackerState,
|
|
49
|
+
WriteOptions,
|
|
50
|
+
} from './core/codec/index.js';
|
|
51
|
+
export { createDecoderCore, createEncoderCore, createLifecycleTracker, eventOutput } from './core/codec/index.js';
|
|
52
|
+
|
|
53
|
+
// Constants
|
|
54
|
+
export {
|
|
55
|
+
DOMAIN_HEADER_PREFIX,
|
|
56
|
+
EVENT_ABORT,
|
|
57
|
+
EVENT_CANCEL,
|
|
58
|
+
EVENT_ERROR,
|
|
59
|
+
EVENT_TURN_END,
|
|
60
|
+
EVENT_TURN_START,
|
|
61
|
+
HEADER_CANCEL_ALL,
|
|
62
|
+
HEADER_CANCEL_CLIENT_ID,
|
|
63
|
+
HEADER_CANCEL_OWN,
|
|
64
|
+
HEADER_CANCEL_TURN_ID,
|
|
65
|
+
HEADER_FORK_OF,
|
|
66
|
+
HEADER_MSG_ID,
|
|
67
|
+
HEADER_PARENT,
|
|
68
|
+
HEADER_ROLE,
|
|
69
|
+
HEADER_STATUS,
|
|
70
|
+
HEADER_STREAM,
|
|
71
|
+
HEADER_STREAM_ID,
|
|
72
|
+
HEADER_TURN_CLIENT_ID,
|
|
73
|
+
HEADER_TURN_ID,
|
|
74
|
+
HEADER_TURN_REASON,
|
|
75
|
+
} from './constants.js';
|
|
76
|
+
|
|
77
|
+
// Utilities
|
|
78
|
+
export type { DomainHeaderReader, DomainHeaderWriter, Stripped } from './utils.js';
|
|
79
|
+
export { getHeaders, headerReader, headerWriter, mergeHeaders, stripUndefined } from './utils.js';
|
|
80
|
+
|
|
81
|
+
// Event emitter
|
|
82
|
+
export { EventEmitter } from './event-emitter.js';
|
|
83
|
+
|
|
84
|
+
// Errors
|
|
85
|
+
export { ErrorCode, errorInfoIs } from './errors.js';
|
|
86
|
+
|
|
87
|
+
// Logger
|
|
88
|
+
export type { LogContext, Logger, LoggerOptions, LogHandler } from './logger.js';
|
|
89
|
+
export { consoleLogger, LogLevel, makeLogger } from './logger.js';
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import * as Ably from 'ably';
|
|
2
|
+
|
|
3
|
+
import { ErrorCode } from './errors.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Structured logger with leveled output and hierarchical context.
|
|
7
|
+
* Implementations filter messages by level and delegate to a {@link LogHandler}.
|
|
8
|
+
*/
|
|
9
|
+
export interface Logger {
|
|
10
|
+
/**
|
|
11
|
+
* Log a message at the trace level.
|
|
12
|
+
* @param message The message to log.
|
|
13
|
+
* @param context The context of the log message as key-value pairs.
|
|
14
|
+
*/
|
|
15
|
+
trace(message: string, context?: LogContext): void;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Log a message at the debug level.
|
|
19
|
+
* @param message The message to log.
|
|
20
|
+
* @param context The context of the log message as key-value pairs.
|
|
21
|
+
*/
|
|
22
|
+
debug(message: string, context?: LogContext): void;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Log a message at the info level.
|
|
26
|
+
* @param message The message to log.
|
|
27
|
+
* @param context The context of the log message as key-value pairs.
|
|
28
|
+
*/
|
|
29
|
+
info(message: string, context?: LogContext): void;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Log a message at the warn level.
|
|
33
|
+
* @param message The message to log.
|
|
34
|
+
* @param context The context of the log message as key-value pairs.
|
|
35
|
+
*/
|
|
36
|
+
warn(message: string, context?: LogContext): void;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Log a message at the error level.
|
|
40
|
+
* @param message The message to log.
|
|
41
|
+
* @param context The context of the log message as key-value pairs.
|
|
42
|
+
*/
|
|
43
|
+
error(message: string, context?: LogContext): void;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Creates a new logger with a context that will be merged with any context provided to individual log calls.
|
|
47
|
+
* The context will be overridden by any matching keys in the individual log call's context.
|
|
48
|
+
* @param context The context to use for all log calls.
|
|
49
|
+
* @returns A new logger instance with the context.
|
|
50
|
+
*/
|
|
51
|
+
withContext(context: LogContext): Logger;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Represents the different levels of logging that can be used.
|
|
56
|
+
*/
|
|
57
|
+
export enum LogLevel {
|
|
58
|
+
/**
|
|
59
|
+
* Something routine and expected has occurred. This level will provide logs for the vast majority of operations
|
|
60
|
+
* and function calls.
|
|
61
|
+
*/
|
|
62
|
+
Trace = 'trace',
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Development information, messages that are useful when trying to debug library behavior,
|
|
66
|
+
* but superfluous to normal operation.
|
|
67
|
+
*/
|
|
68
|
+
Debug = 'debug',
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Informational messages. Operationally significant to the library but not out of the ordinary.
|
|
72
|
+
*/
|
|
73
|
+
Info = 'info',
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Anything that is not immediately an error, but could cause unexpected behavior in the future. For example,
|
|
77
|
+
* passing an invalid value to an option. Indicates that some action should be taken to prevent future errors.
|
|
78
|
+
*/
|
|
79
|
+
Warn = 'warn',
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* A given operation has failed and cannot be automatically recovered. The error may threaten the continuity
|
|
83
|
+
* of operation.
|
|
84
|
+
*/
|
|
85
|
+
Error = 'error',
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* No logging will be performed.
|
|
89
|
+
*/
|
|
90
|
+
Silent = 'silent',
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Represents the context of a log message.
|
|
95
|
+
* It is an object of key-value pairs that can be used to provide additional context to a log message.
|
|
96
|
+
*/
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
98
|
+
export type LogContext = Record<string, any>;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* A function that can be used to handle log messages.
|
|
102
|
+
* @param message The message to log.
|
|
103
|
+
* @param level The log level of the message.
|
|
104
|
+
* @param context The context of the log message as key-value pairs.
|
|
105
|
+
*/
|
|
106
|
+
export type LogHandler = (message: string, level: LogLevel, context?: LogContext) => void;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* A simple console logger that logs messages to the console.
|
|
110
|
+
* @param message The message to log.
|
|
111
|
+
* @param level The log level of the message.
|
|
112
|
+
* @param context - The context of the log message as key-value pairs.
|
|
113
|
+
*/
|
|
114
|
+
export const consoleLogger = (message: string, level: LogLevel, context?: LogContext) => {
|
|
115
|
+
const contextString = context ? `, context: ${JSON.stringify(context)}` : '';
|
|
116
|
+
const formattedMessage = `[${new Date().toISOString()}] ${level.valueOf().toUpperCase()} ably-ai-transport: ${message}${contextString}`;
|
|
117
|
+
|
|
118
|
+
switch (level) {
|
|
119
|
+
case LogLevel.Trace:
|
|
120
|
+
case LogLevel.Debug: {
|
|
121
|
+
console.log(formattedMessage);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
case LogLevel.Info: {
|
|
125
|
+
console.info(formattedMessage);
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case LogLevel.Warn: {
|
|
129
|
+
console.warn(formattedMessage);
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case LogLevel.Error: {
|
|
133
|
+
console.error(formattedMessage);
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
case LogLevel.Silent: {
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Options for creating a logger.
|
|
144
|
+
*/
|
|
145
|
+
export interface LoggerOptions {
|
|
146
|
+
logHandler?: LogHandler;
|
|
147
|
+
logLevel: LogLevel;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export const makeLogger = (options: LoggerOptions): Logger => {
|
|
151
|
+
const logHandler = options.logHandler ?? consoleLogger;
|
|
152
|
+
|
|
153
|
+
return new DefaultLogger(logHandler, options.logLevel);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* A convenient list of log levels as numbers that can be used for easier comparison.
|
|
158
|
+
*/
|
|
159
|
+
enum LogLevelNumber {
|
|
160
|
+
Trace = 0,
|
|
161
|
+
Debug = 1,
|
|
162
|
+
Info = 2,
|
|
163
|
+
Warn = 3,
|
|
164
|
+
Error = 4,
|
|
165
|
+
Silent = 5,
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* A mapping of log levels to their numeric equivalents.
|
|
170
|
+
*/
|
|
171
|
+
const logLevelNumberMap = new Map<LogLevel, LogLevelNumber>([
|
|
172
|
+
[LogLevel.Trace, LogLevelNumber.Trace],
|
|
173
|
+
[LogLevel.Debug, LogLevelNumber.Debug],
|
|
174
|
+
[LogLevel.Info, LogLevelNumber.Info],
|
|
175
|
+
[LogLevel.Warn, LogLevelNumber.Warn],
|
|
176
|
+
[LogLevel.Error, LogLevelNumber.Error],
|
|
177
|
+
[LogLevel.Silent, LogLevelNumber.Silent],
|
|
178
|
+
]);
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* A default logger implementation.
|
|
182
|
+
*/
|
|
183
|
+
class DefaultLogger implements Logger {
|
|
184
|
+
private readonly _handler: LogHandler;
|
|
185
|
+
private readonly _levelNumber: LogLevelNumber;
|
|
186
|
+
private readonly _context?: LogContext;
|
|
187
|
+
|
|
188
|
+
constructor(handler: LogHandler, level: LogLevel, context?: LogContext) {
|
|
189
|
+
this._handler = handler;
|
|
190
|
+
this._context = context;
|
|
191
|
+
|
|
192
|
+
const levelNumber = logLevelNumberMap.get(level);
|
|
193
|
+
if (levelNumber === undefined) {
|
|
194
|
+
throw new Ably.ErrorInfo(`unable to create logger; invalid log level: ${level}`, ErrorCode.InvalidArgument, 400);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
this._levelNumber = levelNumber;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
trace(message: string, context?: LogContext): void {
|
|
201
|
+
this._write(message, LogLevel.Trace, LogLevelNumber.Trace, context);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
debug(message: string, context?: LogContext): void {
|
|
205
|
+
this._write(message, LogLevel.Debug, LogLevelNumber.Debug, context);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
info(message: string, context?: LogContext): void {
|
|
209
|
+
this._write(message, LogLevel.Info, LogLevelNumber.Info, context);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
warn(message: string, context?: LogContext): void {
|
|
213
|
+
this._write(message, LogLevel.Warn, LogLevelNumber.Warn, context);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
error(message: string, context?: LogContext): void {
|
|
217
|
+
this._write(message, LogLevel.Error, LogLevelNumber.Error, context);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
withContext(context: LogContext): Logger {
|
|
221
|
+
// Get the original log level by finding the key in logLevelNumberMap that matches this._levelNumber
|
|
222
|
+
const originalLevel =
|
|
223
|
+
[...logLevelNumberMap.entries()].find(([, value]) => value === this._levelNumber)?.[0] ?? LogLevel.Error;
|
|
224
|
+
|
|
225
|
+
return new DefaultLogger(this._handler, originalLevel, this._mergeContext(context));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private _write(message: string, level: LogLevel, levelNumber: LogLevelNumber, context?: LogContext): void {
|
|
229
|
+
if (levelNumber >= this._levelNumber) {
|
|
230
|
+
this._handler(message, level, this._mergeContext(context));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private _mergeContext(context?: LogContext): LogContext | undefined {
|
|
235
|
+
if (!this._context) {
|
|
236
|
+
return context ?? undefined;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return context ? { ...this._context, ...context } : this._context;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { useAblyMessages } from './use-ably-messages.js';
|
|
2
|
+
export { useActiveTurns } from './use-active-turns.js';
|
|
3
|
+
export { useClientTransport } from './use-client-transport.js';
|
|
4
|
+
export type { ConversationTreeHandle } from './use-conversation-tree.js';
|
|
5
|
+
export { useConversationTree } from './use-conversation-tree.js';
|
|
6
|
+
export { useEdit } from './use-edit.js';
|
|
7
|
+
export type { HistoryHandle } from './use-history.js';
|
|
8
|
+
export { useHistory } from './use-history.js';
|
|
9
|
+
export { useMessages } from './use-messages.js';
|
|
10
|
+
export { useRegenerate } from './use-regenerate.js';
|
|
11
|
+
export { useSend } from './use-send.js';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAblyMessages — reactive raw Ably message log from a ClientTransport.
|
|
3
|
+
*
|
|
4
|
+
* Returns the accumulated raw Ably InboundMessages in chronological order,
|
|
5
|
+
* including both live messages (from the channel subscription) and
|
|
6
|
+
* history-loaded messages (from transport.history() calls).
|
|
7
|
+
*
|
|
8
|
+
* Subscribes to the transport's "ably-message" event and re-reads the
|
|
9
|
+
* list on each update.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type * as Ably from 'ably';
|
|
13
|
+
import { useEffect, useState } from 'react';
|
|
14
|
+
|
|
15
|
+
import type { ClientTransport } from '../core/transport/types.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Subscribe to raw Ably message updates from a client transport.
|
|
19
|
+
* @param transport - The client transport to observe.
|
|
20
|
+
* @returns The accumulated raw Ably messages in chronological order.
|
|
21
|
+
*/
|
|
22
|
+
export const useAblyMessages = <TEvent, TMessage>(
|
|
23
|
+
transport: ClientTransport<TEvent, TMessage>,
|
|
24
|
+
): Ably.InboundMessage[] => {
|
|
25
|
+
const [messages, setMessages] = useState<Ably.InboundMessage[]>(() => transport.getAblyMessages());
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
setMessages(transport.getAblyMessages());
|
|
29
|
+
|
|
30
|
+
const unsub = transport.on('ably-message', () => {
|
|
31
|
+
setMessages(transport.getAblyMessages());
|
|
32
|
+
});
|
|
33
|
+
return unsub;
|
|
34
|
+
}, [transport]);
|
|
35
|
+
|
|
36
|
+
return messages;
|
|
37
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useActiveTurns: reactive view of active turns on the channel,
|
|
3
|
+
* keyed by clientId.
|
|
4
|
+
*
|
|
5
|
+
* Subscribes to transport turn lifecycle events and maintains a
|
|
6
|
+
* Map<clientId, Set<turnId>> that updates on every turn start/end.
|
|
7
|
+
*
|
|
8
|
+
* Generic — works with any codec, not tied to Vercel types.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useEffect, useState } from 'react';
|
|
12
|
+
|
|
13
|
+
import { EVENT_TURN_START } from '../constants.js';
|
|
14
|
+
import type { ClientTransport, TurnLifecycleEvent } from '../core/transport/types.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Returns a reactive Map of all active turns on the channel, keyed by clientId.
|
|
18
|
+
* Updates when turns start or end.
|
|
19
|
+
* @param transport - The client transport to observe, or null/undefined if not yet available.
|
|
20
|
+
* @returns A Map where keys are clientIds and values are Sets of active turnIds.
|
|
21
|
+
*/
|
|
22
|
+
export const useActiveTurns = <TEvent, TMessage>(
|
|
23
|
+
transport: ClientTransport<TEvent, TMessage> | null | undefined,
|
|
24
|
+
): Map<string, Set<string>> => {
|
|
25
|
+
const [turns, setTurns] = useState<Map<string, Set<string>>>(() => new Map());
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (!transport) return;
|
|
29
|
+
|
|
30
|
+
// Initialize from current state
|
|
31
|
+
setTurns(transport.getActiveTurnIds());
|
|
32
|
+
|
|
33
|
+
const unsubscribe = transport.on('turn', (event: TurnLifecycleEvent) => {
|
|
34
|
+
setTurns((prev) => {
|
|
35
|
+
const next = new Map(prev);
|
|
36
|
+
|
|
37
|
+
if (event.type === EVENT_TURN_START) {
|
|
38
|
+
const set = new Set(next.get(event.clientId) ?? []);
|
|
39
|
+
set.add(event.turnId);
|
|
40
|
+
next.set(event.clientId, set);
|
|
41
|
+
} else {
|
|
42
|
+
const set = next.get(event.clientId);
|
|
43
|
+
if (set) {
|
|
44
|
+
set.delete(event.turnId);
|
|
45
|
+
if (set.size === 0) {
|
|
46
|
+
next.delete(event.clientId);
|
|
47
|
+
} else {
|
|
48
|
+
next.set(event.clientId, new Set(set));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return next;
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return unsubscribe;
|
|
58
|
+
}, [transport]);
|
|
59
|
+
|
|
60
|
+
return turns;
|
|
61
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useClientTransport: creates and memoizes a core ClientTransport instance
|
|
3
|
+
* across renders.
|
|
4
|
+
*
|
|
5
|
+
* Stores the instance in a ref so the same transport is returned on every render.
|
|
6
|
+
* The transport manages its own Ably channel subscription in the constructor —
|
|
7
|
+
* this hook adds no subscription logic.
|
|
8
|
+
*
|
|
9
|
+
* The hook does NOT auto-close the transport on unmount. Channel lifecycle is
|
|
10
|
+
* managed by the Ably provider (useChannel), which detaches the channel and
|
|
11
|
+
* clears all subscriptions. Auto-closing would break React Strict Mode
|
|
12
|
+
* (double-mount calls close() on the first cleanup, leaving a dead transport
|
|
13
|
+
* on the second mount). Call transport.close() explicitly if you need to tear
|
|
14
|
+
* down the transport independently of the channel lifecycle.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { useRef } from 'react';
|
|
18
|
+
|
|
19
|
+
import { createClientTransport } from '../core/transport/client-transport.js';
|
|
20
|
+
import type { ClientTransport, ClientTransportOptions } from '../core/transport/types.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Create and memoize a {@link ClientTransport} across renders.
|
|
24
|
+
* @param options - Configuration for the client transport.
|
|
25
|
+
* @returns The memoized transport instance.
|
|
26
|
+
*/
|
|
27
|
+
export const useClientTransport = <TEvent, TMessage>(
|
|
28
|
+
options: ClientTransportOptions<TEvent, TMessage>,
|
|
29
|
+
): ClientTransport<TEvent, TMessage> => {
|
|
30
|
+
const transportRef = useRef<ClientTransport<TEvent, TMessage> | null>(null);
|
|
31
|
+
|
|
32
|
+
if (transportRef.current === null) {
|
|
33
|
+
transportRef.current = createClientTransport(options);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return transportRef.current;
|
|
37
|
+
};
|