march-ai-sdk 0.3.0 → 0.4.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "march-ai-sdk",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "TypeScript SDK for building AI agents in the March AI platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * March Agent SDK - TypeScript framework for building AI agents
3
- *
3
+ *
4
4
  * @packageDocumentation
5
5
  */
6
6
 
@@ -14,12 +14,26 @@ export { Memory } from './memory.js'
14
14
 
15
15
  // Message types
16
16
  export { ConversationMessage } from './conversation-message.js'
17
- export { type Artifact, type ArtifactType, type ArtifactInput, toArtifact } from './artifact.js'
17
+
18
+ // Structural streaming components
19
+ export {
20
+ StructuralStreamer,
21
+ Artifact,
22
+ Surface,
23
+ TextBlock,
24
+ Stepper,
25
+ } from './structural/index.js'
18
26
 
19
27
  // Clients
20
28
  export { GatewayClient } from './gateway-client.js'
21
29
  export { ConversationClient } from './conversation-client.js'
22
- export { CheckpointClient, type CheckpointConfig, type CheckpointData, type CheckpointMetadata, type CheckpointTuple } from './checkpoint-client.js'
30
+ export {
31
+ CheckpointClient,
32
+ type CheckpointConfig,
33
+ type CheckpointData,
34
+ type CheckpointMetadata,
35
+ type CheckpointTuple,
36
+ } from './checkpoint-client.js'
23
37
  export { AgentStateClient, type AgentStateResponse } from './agent-state-client.js'
24
38
  export { MemoryClient } from './memory-client.js'
25
39
  export { AttachmentClient, createAttachmentInfo, type AttachmentInfo } from './attachment-client.js'
@@ -67,4 +81,4 @@ export {
67
81
  } from './exceptions.js'
68
82
 
69
83
  // Version
70
- export const VERSION = '0.3.0'
84
+ export const VERSION = '0.4.0'
package/src/streamer.ts CHANGED
@@ -6,8 +6,7 @@
6
6
  import type { Message } from './message.js'
7
7
  import type { GatewayClient } from './gateway-client.js'
8
8
  import type { ConversationClient } from './conversation-client.js'
9
- import type { Artifact, ArtifactInput } from './artifact.js'
10
- import { toArtifact } from './artifact.js'
9
+ import type { StructuralStreamer } from './structural/base.js'
11
10
  import type { StreamOptions } from './types.js'
12
11
 
13
12
  /**
@@ -23,9 +22,7 @@ export class Streamer {
23
22
 
24
23
  private responseSchema?: Record<string, unknown>
25
24
  private messageMetadata?: Record<string, unknown>
26
- private artifacts: Artifact[] = []
27
25
  private streamedContent: string = ''
28
- private firstChunkSent: boolean = false
29
26
  private finished: boolean = false
30
27
 
31
28
  constructor(options: {
@@ -61,19 +58,22 @@ export class Streamer {
61
58
  }
62
59
 
63
60
  /**
64
- * Add an artifact to the message (fluent API).
61
+ * Bind a structural streamer to this streamer for event sending.
62
+ *
63
+ * Returns the structural object itself with streaming capability enabled.
64
+ *
65
+ * @param structural - StructuralStreamer instance (Artifact, Surface, etc.)
66
+ * @returns The same structural object, now bound to this streamer
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * const artifact = new Artifact()
71
+ * s.streamBy(artifact).generating("Creating...")
72
+ * s.streamBy(artifact).done({ url: "...", type: "image" })
73
+ * ```
65
74
  */
66
- addArtifact(artifact: ArtifactInput): this {
67
- this.artifacts.push(toArtifact(artifact))
68
- return this
69
- }
70
-
71
- /**
72
- * Set all artifacts at once (replaces any existing).
73
- */
74
- setArtifacts(artifacts: ArtifactInput[]): this {
75
- this.artifacts = artifacts.map(toArtifact)
76
- return this
75
+ streamBy<T extends StructuralStreamer>(structural: T): T {
76
+ return structural._bindStreamer(this)
77
77
  }
78
78
 
79
79
  /**
@@ -91,7 +91,7 @@ export class Streamer {
91
91
  this.streamedContent += content
92
92
  }
93
93
 
94
- this.send(content, false, persist, eventType)
94
+ this._send(content, false, persist, eventType)
95
95
  }
96
96
 
97
97
  /**
@@ -120,7 +120,7 @@ export class Streamer {
120
120
  }
121
121
 
122
122
  // Send final done message
123
- this.send('', true, false)
123
+ this._send('', true, false)
124
124
 
125
125
  // Set pending response schema on conversation
126
126
  if (this.responseSchema && this.conversationClient) {
@@ -135,8 +135,9 @@ export class Streamer {
135
135
 
136
136
  /**
137
137
  * Send message to router via gateway.
138
+ * This method is used internally and by structural streamers.
138
139
  */
139
- private send(
140
+ _send(
140
141
  content: string,
141
142
  done: boolean,
142
143
  persist: boolean = true,
@@ -155,18 +156,12 @@ export class Streamer {
155
156
  headers.eventType = eventType
156
157
  }
157
158
 
158
- // Include metadata, artifacts, and schema on first chunk (only once)
159
- if (!this.firstChunkSent) {
160
- this.firstChunkSent = true
161
-
159
+ // Include metadata and schema on first chunk (only once)
160
+ if (this.streamedContent.length === 0 && !this.finished) {
162
161
  if (this.messageMetadata) {
163
162
  headers.messageMetadata = JSON.stringify(this.messageMetadata)
164
163
  }
165
164
 
166
- if (this.artifacts.length > 0) {
167
- headers.artifacts = JSON.stringify(this.artifacts)
168
- }
169
-
170
165
  if (this.responseSchema) {
171
166
  headers.responseSchema = JSON.stringify(this.responseSchema)
172
167
  }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * March Agent SDK - Artifact Structural Streamer
3
+ * Port of Python march_agent/structural/artifact.py
4
+ *
5
+ * Artifact structural streamer for file/image/iframe artifacts.
6
+ */
7
+
8
+ import { StructuralStreamer, generateShortId } from './base.js'
9
+
10
+ /**
11
+ * Manages artifact lifecycle: generating -> done.
12
+ *
13
+ * Artifacts are files, images, iframes that are generated and displayed.
14
+ * Artifact data is persisted to database on done().
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const artifact = new Artifact() // ID auto-generated
19
+ * s.streamBy(artifact).generating("Creating chart...", 0.5)
20
+ * s.streamBy(artifact).done({
21
+ * url: "https://example.com/chart.png",
22
+ * type: "image",
23
+ * title: "Sales Chart"
24
+ * })
25
+ * ```
26
+ */
27
+ export class Artifact extends StructuralStreamer {
28
+ protected _generateId(): string {
29
+ return `artifact-${generateShortId()}`
30
+ }
31
+
32
+ getEventTypePrefix(): string {
33
+ return 'artifact'
34
+ }
35
+
36
+ /**
37
+ * Signal artifact is being generated.
38
+ *
39
+ * @param message - Status message (e.g., "Creating chart...")
40
+ * @param progress - Progress value 0.0-1.0
41
+ * @returns this for method chaining
42
+ */
43
+ generating(message?: string, progress?: number): this {
44
+ const data: Record<string, unknown> = {}
45
+ if (message !== undefined) {
46
+ data.message = message
47
+ }
48
+ if (progress !== undefined) {
49
+ data.progress = progress
50
+ }
51
+ return this._sendEvent('generating', data)
52
+ }
53
+
54
+ /**
55
+ * Signal artifact is complete and persist to database.
56
+ *
57
+ * @param options - Artifact completion options
58
+ * @param options.url - URL to artifact
59
+ * @param options.type - Artifact type (image, iframe, document, video, audio, code, link, file)
60
+ * @param options.title - Display title
61
+ * @param options.description - Optional description
62
+ * @param options.metadata - Additional metadata (size, mimeType, dimensions, etc.)
63
+ * @returns this for method chaining
64
+ */
65
+ done(options: {
66
+ url: string
67
+ type: string
68
+ title?: string
69
+ description?: string
70
+ metadata?: Record<string, unknown>
71
+ }): this {
72
+ const data: Record<string, unknown> = {
73
+ url: options.url,
74
+ type: options.type,
75
+ }
76
+ if (options.title) {
77
+ data.title = options.title
78
+ }
79
+ if (options.description) {
80
+ data.description = options.description
81
+ }
82
+ if (options.metadata) {
83
+ data.metadata = options.metadata
84
+ }
85
+ return this._sendEvent('done', data)
86
+ }
87
+
88
+ /**
89
+ * Signal artifact generation failed.
90
+ *
91
+ * @param message - Error message
92
+ * @returns this for method chaining
93
+ */
94
+ error(message: string): this {
95
+ return this._sendEvent('error', { message })
96
+ }
97
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * March Agent SDK - Structural Streaming Base
3
+ * Port of Python march_agent/structural/base.py
4
+ *
5
+ * Base class for structural streaming objects.
6
+ */
7
+
8
+ import type { Streamer } from '../streamer.js'
9
+
10
+ /**
11
+ * Abstract base class for structural streaming objects.
12
+ *
13
+ * Structural streamers generate events but don't hold streaming state.
14
+ * The Streamer binds to them via streamBy() to enable streaming.
15
+ */
16
+ export abstract class StructuralStreamer {
17
+ readonly id: string
18
+ protected _streamer?: Streamer
19
+
20
+ constructor(id?: string) {
21
+ this.id = id ?? this._generateId()
22
+ }
23
+
24
+ /**
25
+ * Generate a unique ID for this streamer type.
26
+ */
27
+ protected abstract _generateId(): string
28
+
29
+ /**
30
+ * Get the event type prefix (e.g., 'artifact', 'text_block').
31
+ */
32
+ abstract getEventTypePrefix(): string
33
+
34
+ /**
35
+ * Bind this structural streamer to a Streamer instance.
36
+ * Called by Streamer.streamBy(). Returns self for chaining.
37
+ */
38
+ _bindStreamer(streamer: Streamer): this {
39
+ this._streamer = streamer
40
+ return this
41
+ }
42
+
43
+ /**
44
+ * Send an event through the bound streamer.
45
+ *
46
+ * Creates event payload and sends via streamer._send().
47
+ * Returns self for method chaining.
48
+ */
49
+ protected _sendEvent(action: string, data: Record<string, unknown> = {}): this {
50
+ if (!this._streamer) {
51
+ throw new Error(
52
+ `${this.constructor.name} not bound to a Streamer. ` +
53
+ `Call streamer.streamBy() first.`
54
+ )
55
+ }
56
+
57
+ // Build event body
58
+ const body = { id: this.id, ...data }
59
+
60
+ // Build event type
61
+ const eventType = `${this.getEventTypePrefix()}:${action}`
62
+
63
+ // Send through streamer using existing _send() method
64
+ // content = stringified JSON body
65
+ // eventType = structural event type
66
+ // persist = false (structural events not persisted as content)
67
+ this._streamer._send(
68
+ JSON.stringify(body),
69
+ false, // done
70
+ false, // persist
71
+ eventType
72
+ )
73
+
74
+ return this
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Generate a short random hex string for IDs.
80
+ */
81
+ export function generateShortId(): string {
82
+ return Math.random().toString(16).slice(2, 10)
83
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * March Agent SDK - Structural Streaming Module
3
+ * Port of Python march_agent/structural/__init__.py
4
+ *
5
+ * Exports all structural streaming components.
6
+ */
7
+
8
+ export { StructuralStreamer, generateShortId } from './base.js'
9
+ export { Artifact } from './artifact.js'
10
+ export { Surface } from './surface.js'
11
+ export { TextBlock } from './text-block.js'
12
+ export { Stepper } from './stepper.js'
@@ -0,0 +1,131 @@
1
+ /**
2
+ * March Agent SDK - Stepper Structural Streamer
3
+ * Port of Python march_agent/structural/stepper.py
4
+ *
5
+ * Stepper structural streamer for multi-step progress indicators.
6
+ */
7
+
8
+ import { StructuralStreamer, generateShortId } from './base.js'
9
+
10
+ /**
11
+ * Manages multi-step progress indicator.
12
+ *
13
+ * Stepper events are NOT persisted to database.
14
+ * IDs are auto-generated - no need to provide them manually.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const stepper = new Stepper({ steps: ["Fetch", "Process", "Report"] }) // ID auto-generated
19
+ * s.streamBy(stepper).startStep(0)
20
+ * s.streamBy(stepper).completeStep(0)
21
+ * s.streamBy(stepper).startStep(1)
22
+ * s.streamBy(stepper).addStep("Verify") // Dynamic step
23
+ * s.streamBy(stepper).completeStep(1)
24
+ * s.streamBy(stepper).done()
25
+ * ```
26
+ */
27
+ export class Stepper extends StructuralStreamer {
28
+ readonly steps: string[]
29
+ private _initialized: boolean = false
30
+
31
+ constructor(options?: { id?: string; steps?: string[] }) {
32
+ super(options?.id)
33
+ this.steps = options?.steps ?? []
34
+ }
35
+
36
+ protected _generateId(): string {
37
+ return `stepper-${generateShortId()}`
38
+ }
39
+
40
+ getEventTypePrefix(): string {
41
+ return 'stepper'
42
+ }
43
+
44
+ /**
45
+ * Send initialization event with steps if not already sent.
46
+ * This is automatically called before any other stepper event.
47
+ */
48
+ private _ensureInitialized(): this {
49
+ if (!this._initialized && this.steps.length > 0) {
50
+ this._sendEvent('init', { steps: this.steps })
51
+ this._initialized = true
52
+ }
53
+ return this
54
+ }
55
+
56
+ /**
57
+ * Mark step as in progress.
58
+ *
59
+ * @param index - Step index to start
60
+ * @returns this for method chaining
61
+ */
62
+ startStep(index: number): this {
63
+ this._ensureInitialized()
64
+ return this._sendEvent('start_step', { index })
65
+ }
66
+
67
+ /**
68
+ * Mark step as complete.
69
+ *
70
+ * @param index - Step index to complete
71
+ * @returns this for method chaining
72
+ */
73
+ completeStep(index: number): this {
74
+ this._ensureInitialized()
75
+ return this._sendEvent('complete_step', { index })
76
+ }
77
+
78
+ /**
79
+ * Mark step as failed.
80
+ *
81
+ * @param index - Step index that failed
82
+ * @param error - Optional error message
83
+ * @returns this for method chaining
84
+ */
85
+ failStep(index: number, error?: string): this {
86
+ this._ensureInitialized()
87
+ const data: Record<string, unknown> = { index }
88
+ if (error) {
89
+ data.error = error
90
+ }
91
+ return this._sendEvent('fail_step', data)
92
+ }
93
+
94
+ /**
95
+ * Add a new step dynamically.
96
+ *
97
+ * @param label - Step label
98
+ * @param index - Optional position to insert at
99
+ * @returns this for method chaining
100
+ */
101
+ addStep(label: string, index?: number): this {
102
+ this._ensureInitialized()
103
+ const data: Record<string, unknown> = { label }
104
+ if (index !== undefined) {
105
+ data.index = index
106
+ }
107
+ return this._sendEvent('add_step', data)
108
+ }
109
+
110
+ /**
111
+ * Update step label.
112
+ *
113
+ * @param index - Step index to update
114
+ * @param label - New label
115
+ * @returns this for method chaining
116
+ */
117
+ updateStepLabel(index: number, label: string): this {
118
+ this._ensureInitialized()
119
+ return this._sendEvent('update_step_label', { index, label })
120
+ }
121
+
122
+ /**
123
+ * Mark stepper as complete (all steps finished).
124
+ *
125
+ * @returns this for method chaining
126
+ */
127
+ done(): this {
128
+ this._ensureInitialized()
129
+ return this._sendEvent('done')
130
+ }
131
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * March Agent SDK - Surface Structural Streamer
3
+ * Port of Python march_agent/structural/surface.py
4
+ *
5
+ * Surface structural streamer for embedded interactive components.
6
+ */
7
+
8
+ import { StructuralStreamer, generateShortId } from './base.js'
9
+
10
+ /**
11
+ * Manages embedded surface lifecycle (similar to Artifact).
12
+ *
13
+ * Surfaces are embedded interactive components (iframes, embeds).
14
+ * Surface data is persisted to database as artifacts with surface type.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const surface = new Surface()
19
+ * s.streamBy(surface).generating("Loading calendar...")
20
+ * s.streamBy(surface).done({ url: "https://cal.com/embed", type: "iframe" })
21
+ * ```
22
+ */
23
+ export class Surface extends StructuralStreamer {
24
+ protected _generateId(): string {
25
+ return `surface-${generateShortId()}`
26
+ }
27
+
28
+ getEventTypePrefix(): string {
29
+ return 'surface'
30
+ }
31
+
32
+ /**
33
+ * Signal surface is loading.
34
+ *
35
+ * @param message - Status message (e.g., "Loading calendar...")
36
+ * @param progress - Progress value 0.0-1.0
37
+ * @returns this for method chaining
38
+ */
39
+ generating(message?: string, progress?: number): this {
40
+ const data: Record<string, unknown> = {}
41
+ if (message !== undefined) {
42
+ data.message = message
43
+ }
44
+ if (progress !== undefined) {
45
+ data.progress = progress
46
+ }
47
+ return this._sendEvent('generating', data)
48
+ }
49
+
50
+ /**
51
+ * Signal surface is ready and persist to database.
52
+ *
53
+ * @param options - Surface completion options
54
+ * @param options.url - URL to surface
55
+ * @param options.type - Surface type (default: iframe)
56
+ * @param options.title - Display title
57
+ * @param options.description - Optional description
58
+ * @param options.metadata - Additional metadata
59
+ * @returns this for method chaining
60
+ */
61
+ done(options: {
62
+ url: string
63
+ type?: string
64
+ title?: string
65
+ description?: string
66
+ metadata?: Record<string, unknown>
67
+ }): this {
68
+ const data: Record<string, unknown> = {
69
+ url: options.url,
70
+ type: options.type ?? 'iframe',
71
+ }
72
+ if (options.title) {
73
+ data.title = options.title
74
+ }
75
+ if (options.description) {
76
+ data.description = options.description
77
+ }
78
+ if (options.metadata) {
79
+ data.metadata = options.metadata
80
+ }
81
+ return this._sendEvent('done', data)
82
+ }
83
+
84
+ /**
85
+ * Signal surface loading failed.
86
+ *
87
+ * @param message - Error message
88
+ * @returns this for method chaining
89
+ */
90
+ error(message: string): this {
91
+ return this._sendEvent('error', { message })
92
+ }
93
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * March Agent SDK - TextBlock Structural Streamer
3
+ * Port of Python march_agent/structural/text_block.py
4
+ *
5
+ * TextBlock structural streamer for collapsible text content.
6
+ */
7
+
8
+ import { StructuralStreamer, generateShortId } from './base.js'
9
+
10
+ /**
11
+ * Manages collapsible text block with title and body.
12
+ *
13
+ * Both title and body support streaming (append) and update (replace).
14
+ * TextBlock events are NOT persisted to database.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const block = new TextBlock() // ID auto-generated
19
+ * s.streamBy(block).setVariant("thinking")
20
+ * s.streamBy(block).streamTitle("Deep ")
21
+ * s.streamBy(block).streamTitle("Analysis...")
22
+ * s.streamBy(block).streamBody("Step 1: Check patterns\n")
23
+ * s.streamBy(block).streamBody("Step 2: Validate\n")
24
+ * s.streamBy(block).updateTitle("Analysis Complete")
25
+ * s.streamBy(block).done()
26
+ * ```
27
+ */
28
+ export class TextBlock extends StructuralStreamer {
29
+ readonly initialTitle?: string
30
+
31
+ constructor(options?: { id?: string; title?: string }) {
32
+ super(options?.id)
33
+ this.initialTitle = options?.title
34
+ }
35
+
36
+ protected _generateId(): string {
37
+ return `text_block-${generateShortId()}`
38
+ }
39
+
40
+ getEventTypePrefix(): string {
41
+ return 'text_block'
42
+ }
43
+
44
+ /**
45
+ * Stream title content (appends to existing).
46
+ *
47
+ * @param content - Content to append to title
48
+ * @returns this for method chaining
49
+ */
50
+ streamTitle(content: string): this {
51
+ return this._sendEvent('stream_title', { content })
52
+ }
53
+
54
+ /**
55
+ * Stream body content (appends to existing).
56
+ *
57
+ * @param content - Content to append to body
58
+ * @returns this for method chaining
59
+ */
60
+ streamBody(content: string): this {
61
+ return this._sendEvent('stream_body', { content })
62
+ }
63
+
64
+ /**
65
+ * Replace entire title.
66
+ *
67
+ * @param title - New title (replaces existing)
68
+ * @returns this for method chaining
69
+ */
70
+ updateTitle(title: string): this {
71
+ return this._sendEvent('update_title', { title })
72
+ }
73
+
74
+ /**
75
+ * Replace entire body.
76
+ *
77
+ * @param body - New body (replaces existing)
78
+ * @returns this for method chaining
79
+ */
80
+ updateBody(body: string): this {
81
+ return this._sendEvent('update_body', { body })
82
+ }
83
+
84
+ /**
85
+ * Set visual variant.
86
+ *
87
+ * @param variant - Visual style (thinking, note, warning, error, success)
88
+ * @returns this for method chaining
89
+ */
90
+ setVariant(variant: 'thinking' | 'note' | 'warning' | 'error' | 'success' | string): this {
91
+ return this._sendEvent('set_variant', { variant })
92
+ }
93
+
94
+ /**
95
+ * Mark text block as complete.
96
+ *
97
+ * @returns this for method chaining
98
+ */
99
+ done(): this {
100
+ return this._sendEvent('done')
101
+ }
102
+ }