-
Notifications
You must be signed in to change notification settings - Fork 0
transport: unify server-side turn error handling #60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -328,14 +328,13 @@ class DefaultServerTransport<TEvent, TMessage> implements ServerTransport<TEvent | |
| error instanceof Ably.ErrorInfo ? error : undefined, | ||
| ); | ||
| logger?.error('Turn.start(); failed to publish turn-start', { turnId }); | ||
| turnOnError?.(errInfo); | ||
| throw errInfo; | ||
| } | ||
|
|
||
| logger?.debug('Turn.start(); turn started', { turnId }); | ||
| }, | ||
|
|
||
| // Spec: AIT-ST5, AIT-ST5a, AIT-ST5b | ||
| // Spec: AIT-ST5, AIT-ST5a, AIT-ST5b, AIT-ST5c | ||
| addMessages: async (nodes: MessageNode<TMessage>[], opts?: AddMessageOptions): Promise<AddMessagesResult> => { | ||
| logger?.trace('Turn.addMessages();', { turnId, count: nodes.length }); | ||
|
|
||
|
|
@@ -350,35 +349,47 @@ class DefaultServerTransport<TEvent, TMessage> implements ServerTransport<TEvent | |
|
|
||
| const msgIds: string[] = []; | ||
|
|
||
| for (const node of nodes) { | ||
| // Build transport headers from the node's typed fields, then merge | ||
| // any extra headers from the node (e.g. domain-specific headers). | ||
| const headers = mergeHeaders( | ||
| buildTransportHeaders({ | ||
| role: 'user', | ||
| turnId, | ||
| msgId: node.msgId, | ||
| turnClientId: opts?.clientId, | ||
| parent: node.parentId ?? turnParent, | ||
| forkOf: node.forkOf ?? turnForkOf, | ||
| }), | ||
| node.headers, | ||
| ); | ||
|
|
||
| const encoder = codec.createEncoder(channel, { | ||
| extras: { headers }, | ||
| onMessage, | ||
| }); | ||
| try { | ||
| for (const node of nodes) { | ||
| // Build transport headers from the node's typed fields, then merge | ||
| // any extra headers from the node (e.g. domain-specific headers). | ||
| const headers = mergeHeaders( | ||
| buildTransportHeaders({ | ||
| role: 'user', | ||
| turnId, | ||
| msgId: node.msgId, | ||
| turnClientId: opts?.clientId, | ||
| parent: node.parentId ?? turnParent, | ||
| forkOf: node.forkOf ?? turnForkOf, | ||
| }), | ||
| node.headers, | ||
| ); | ||
|
|
||
| const encoder = codec.createEncoder(channel, { | ||
| extras: { headers }, | ||
| onMessage, | ||
| }); | ||
|
|
||
| await encoder.writeMessages([node.message], opts?.clientId ? { clientId: opts.clientId } : undefined); | ||
| await encoder.writeMessages([node.message], opts?.clientId ? { clientId: opts.clientId } : undefined); | ||
|
|
||
| msgIds.push(node.msgId); | ||
| msgIds.push(node.msgId); | ||
| } | ||
| } catch (error) { | ||
| const errInfo = new Ably.ErrorInfo( | ||
| `unable to publish messages for turn ${turnId}; ${error instanceof Error ? error.message : String(error)}`, | ||
| ErrorCode.TurnLifecycleError, | ||
| 500, | ||
| error instanceof Ably.ErrorInfo ? error : undefined, | ||
| ); | ||
| logger?.error('Turn.addMessages(); publish failed', { turnId }); | ||
| throw errInfo; | ||
|
Comment on lines
+378
to
+385
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should expose this error twice; once in the onError callback and once thrown from here. 🤔 |
||
| } | ||
|
|
||
| logger?.debug('Turn.addMessages(); messages published', { turnId, count: nodes.length }); | ||
| return { msgIds }; | ||
| }, | ||
|
|
||
| // Spec: AIT-ST5c | ||
| addEvents: async (nodes: EventsNode<TEvent>[]): Promise<void> => { | ||
| logger?.trace('Turn.addEvents();', { turnId, count: nodes.length }); | ||
|
|
||
|
|
@@ -393,31 +404,42 @@ class DefaultServerTransport<TEvent, TMessage> implements ServerTransport<TEvent | |
|
|
||
| const turnOwnerClientId = turnManager.getClientId(turnId); | ||
|
|
||
| for (const node of nodes) { | ||
| const headers = buildTransportHeaders({ | ||
| role: 'assistant', | ||
| turnId, | ||
| msgId: node.msgId, | ||
| turnClientId: turnOwnerClientId, | ||
| amend: node.msgId, | ||
| }); | ||
| try { | ||
| for (const node of nodes) { | ||
| const headers = buildTransportHeaders({ | ||
| role: 'assistant', | ||
| turnId, | ||
| msgId: node.msgId, | ||
| turnClientId: turnOwnerClientId, | ||
| amend: node.msgId, | ||
| }); | ||
|
|
||
| const encoder = codec.createEncoder(channel, { | ||
| extras: { headers }, | ||
| onMessage, | ||
| }); | ||
| const encoder = codec.createEncoder(channel, { | ||
| extras: { headers }, | ||
| onMessage, | ||
| }); | ||
|
|
||
| for (const event of node.events) { | ||
| await encoder.writeEvent(event); | ||
| } | ||
| for (const event of node.events) { | ||
| await encoder.writeEvent(event); | ||
| } | ||
|
|
||
| await encoder.close(); | ||
| await encoder.close(); | ||
| } | ||
| } catch (error) { | ||
| const errInfo = new Ably.ErrorInfo( | ||
| `unable to publish events for turn ${turnId}; ${error instanceof Error ? error.message : String(error)}`, | ||
| ErrorCode.TurnLifecycleError, | ||
| 500, | ||
| error instanceof Ably.ErrorInfo ? error : undefined, | ||
| ); | ||
| logger?.error('Turn.addEvents(); publish failed', { turnId }); | ||
| throw errInfo; | ||
| } | ||
|
|
||
| logger?.debug('Turn.addEvents(); events published', { turnId, count: nodes.length }); | ||
| }, | ||
|
|
||
| // Spec: AIT-ST6, AIT-ST6a, AIT-ST6b, AIT-ST6b1, AIT-ST6b2, AIT-ST6b3, AIT-ST6c | ||
| // Spec: AIT-ST6, AIT-ST6a, AIT-ST6b, AIT-ST6b1, AIT-ST6b2, AIT-ST6b3, AIT-ST6b4, AIT-ST6c | ||
| streamResponse: async ( | ||
| stream: ReadableStream<TEvent>, | ||
| streamOpts?: StreamResponseOptions, | ||
|
|
@@ -455,11 +477,22 @@ class DefaultServerTransport<TEvent, TMessage> implements ServerTransport<TEvent | |
|
|
||
| const result = await pipeStream(stream, encoder, signal, onAbort, logger); | ||
|
|
||
| if (result.error) { | ||
| const errInfo = new Ably.ErrorInfo( | ||
| `unable to stream response for turn ${turnId}; ${result.error.message}`, | ||
| ErrorCode.StreamError, | ||
| 500, | ||
| result.error instanceof Ably.ErrorInfo ? result.error : undefined, | ||
| ); | ||
|
Comment on lines
+481
to
+486
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This re-wraps errors thrown from /**
* The source event stream threw during piping (e.g. LLM provider rate
* limit, model error, network failure).
*/ |
||
| logger?.error('Turn.streamResponse(); stream error', { turnId }); | ||
| turnOnError?.(errInfo); | ||
| } | ||
|
|
||
| logger?.debug('Turn.streamResponse(); stream finished', { turnId, reason: result.reason }); | ||
| return result; | ||
| }, | ||
|
|
||
| // Spec: AIT-ST7, AIT-ST7a | ||
| // Spec: AIT-ST7, AIT-ST7a, AIT-ST7b | ||
| end: async (reason: TurnEndReason): Promise<void> => { | ||
| logger?.trace('Turn.end();', { turnId, reason }); | ||
|
|
||
|
|
@@ -483,7 +516,6 @@ class DefaultServerTransport<TEvent, TMessage> implements ServerTransport<TEvent | |
| error instanceof Ably.ErrorInfo ? error : undefined, | ||
| ); | ||
| logger?.error('Turn.end(); failed to publish turn-end', { turnId }); | ||
| turnOnError?.(errInfo); | ||
| throw errInfo; | ||
| } finally { | ||
| registeredTurns.delete(turnId); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.