feat(core): add stream object api (#12841)

Close [AI-193](https://linear.app/affine-design/issue/AI-193)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added support for streaming structured AI chat responses as objects,
enabling richer and more interactive chat experiences.
- Chat messages now include a new field displaying structured stream
objects, such as reasoning steps, text deltas, tool calls, and tool
results.
- GraphQL APIs and queries updated to expose these structured streaming
objects in chat histories.
  - Introduced a new streaming chat endpoint for object-based responses.

- **Bug Fixes**
- Improved error handling for streaming responses to ensure more robust
and informative error reporting.

- **Refactor**
- Centralized and streamlined session preparation and streaming logic
for AI chat providers.
  - Unified streaming setup across multiple AI model providers.

- **Tests**
- Extended test coverage for streaming object responses to ensure
reliability and correctness.

- **Documentation**
- Updated type definitions and schemas to reflect new streaming object
capabilities in both backend and frontend code.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: DarkSky <25152247+darkskygit@users.noreply.github.com>
This commit is contained in:
Wu Yue
2025-06-19 09:13:18 +08:00
committed by GitHub
parent ce951ec316
commit 6169cdab3a
24 changed files with 722 additions and 149 deletions
@@ -34,7 +34,7 @@ import { Admin } from '../../core/common';
import { AccessController } from '../../core/permission';
import { UserType } from '../../core/user';
import { PromptService } from './prompt';
import { PromptMessage } from './providers';
import { PromptMessage, StreamObject } from './providers';
import { ChatSessionService } from './session';
import { CopilotStorage } from './storage';
import {
@@ -168,6 +168,27 @@ class QueryChatHistoriesInput implements Partial<ListHistoriesOptions> {
// ================== Return Types ==================
@ObjectType('StreamObject')
class StreamObjectType {
@Field(() => String)
type!: string;
@Field(() => String, { nullable: true })
textDelta?: string;
@Field(() => String, { nullable: true })
toolCallId?: string;
@Field(() => String, { nullable: true })
toolName?: string;
@Field(() => GraphQLJSON, { nullable: true })
args?: any;
@Field(() => GraphQLJSON, { nullable: true })
result?: any;
}
@ObjectType('ChatMessage')
class ChatMessageType implements Partial<ChatMessage> {
// id will be null if message is a prompt message
@@ -180,6 +201,9 @@ class ChatMessageType implements Partial<ChatMessage> {
@Field(() => String)
content!: string;
@Field(() => [StreamObjectType], { nullable: true })
streamObjects!: StreamObject[];
@Field(() => [String], { nullable: true })
attachments!: string[];