@aphrody/m3-ai 3.3.2 → 3.3.5

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 (56) hide show
  1. package/README.md +29 -7
  2. package/dist/A2APeerInspector.d.ts +41 -0
  3. package/dist/A2APeerInspector.d.ts.map +1 -0
  4. package/dist/A2ATaskTimeline.d.ts +28 -0
  5. package/dist/A2ATaskTimeline.d.ts.map +1 -0
  6. package/dist/AgUiEventStream.d.ts +87 -0
  7. package/dist/AgUiEventStream.d.ts.map +1 -0
  8. package/dist/GeminiAccountMenu.d.ts +55 -0
  9. package/dist/GeminiAccountMenu.d.ts.map +1 -0
  10. package/dist/GeminiPersonalContextEdit.d.ts +29 -0
  11. package/dist/GeminiPersonalContextEdit.d.ts.map +1 -0
  12. package/dist/GoogleSpotlightBar.d.ts +32 -0
  13. package/dist/GoogleSpotlightBar.d.ts.map +1 -0
  14. package/dist/Markdown.d.ts +2 -1
  15. package/dist/Markdown.d.ts.map +1 -1
  16. package/dist/McpAppCard.d.ts +24 -0
  17. package/dist/McpAppCard.d.ts.map +1 -0
  18. package/dist/ModelSelector.d.ts.map +1 -1
  19. package/dist/TaskRow.d.ts.map +1 -1
  20. package/dist/ToolCall.d.ts.map +1 -1
  21. package/dist/index.d.ts +15 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +3629 -1950
  24. package/dist/index.js.map +32 -1
  25. package/package.json +17 -20
  26. package/src/A2APeerInspector.tsx +370 -0
  27. package/src/A2ATaskTimeline.tsx +218 -0
  28. package/src/AgUiEventStream.ts +492 -0
  29. package/src/ChatComposer.tsx +261 -0
  30. package/src/ConversationList.tsx +207 -0
  31. package/src/GeminiAccountMenu.tsx +212 -0
  32. package/src/GeminiPersonalContextEdit.tsx +147 -0
  33. package/src/GoogleSpotlightBar.tsx +165 -0
  34. package/src/InlineBranchCard.tsx +97 -0
  35. package/src/InlineWorkerCard.tsx +182 -0
  36. package/src/Markdown.tsx +769 -0
  37. package/src/McpAppCard.tsx +161 -0
  38. package/src/MemoryPanel.tsx +176 -0
  39. package/src/MessageBubble.tsx +143 -0
  40. package/src/ModelSelector.tsx +319 -0
  41. package/src/ModelUnavailable.tsx +116 -0
  42. package/src/TaskCreateForm.tsx +123 -0
  43. package/src/TaskDetail.tsx +224 -0
  44. package/src/TaskList.tsx +136 -0
  45. package/src/TaskPriorityIcon.tsx +39 -0
  46. package/src/TaskRow.tsx +177 -0
  47. package/src/TaskStatusIcon.tsx +39 -0
  48. package/src/ToolCall.tsx +644 -0
  49. package/src/chatIcons.tsx +105 -0
  50. package/src/chatLabels.ts +243 -0
  51. package/src/citations.tsx +286 -0
  52. package/src/index.ts +173 -0
  53. package/src/internal.tsx +107 -0
  54. package/src/openaiChatStream.ts +400 -0
  55. package/src/types.ts +212 -0
  56. package/src/useOpenAIChat.ts +126 -0
package/src/types.ts ADDED
@@ -0,0 +1,212 @@
1
+ // SPDX-License-Identifier: Apache-2.0 AND MIT
2
+ // Derived from SpaceUI (packages/aphrody-spaceui/packages/ai/src/types.ts), MIT.
3
+
4
+ // Tool execution types
5
+ export type ToolCallStatus = "running" | "completed" | "error";
6
+
7
+ export interface ToolCallPair {
8
+ id: string;
9
+ name: string;
10
+ argsRaw: string;
11
+ args: Record<string, unknown> | null;
12
+ resultRaw: string | null;
13
+ result: Record<string, unknown> | null;
14
+ status: ToolCallStatus;
15
+ /** Human-readable summary provided by live opencode parts */
16
+ title?: string | null;
17
+ }
18
+
19
+ // Transcript step types — discriminated union matching the real API schema
20
+
21
+ export type ActionContent =
22
+ | { type: "text"; text: string }
23
+ | { type: "tool_call"; id: string; name: string; args: string };
24
+
25
+ export type TranscriptStep =
26
+ | { type: "action"; content: ActionContent[] }
27
+ | { type: "user_text"; text: string }
28
+ | { type: "system_text"; text: string }
29
+ | { type: "tool_result"; call_id: string; name: string; text: string };
30
+
31
+ // Paired transcript item for rendering
32
+
33
+ export type TranscriptItem = { kind: "text"; text: string } | { kind: "tool"; pair: ToolCallPair };
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Task board types
37
+ // ---------------------------------------------------------------------------
38
+
39
+ export type TaskStatus = "pending_approval" | "backlog" | "ready" | "in_progress" | "done";
40
+ export type TaskPriority = "critical" | "high" | "medium" | "low";
41
+
42
+ export interface Subtask {
43
+ title: string;
44
+ completed: boolean;
45
+ }
46
+
47
+ export interface Task {
48
+ id: string;
49
+ task_number: number;
50
+ title: string;
51
+ description?: string | null;
52
+ status: TaskStatus;
53
+ priority: TaskPriority;
54
+ owner_agent_id: string;
55
+ assigned_agent_id: string;
56
+ subtasks: Subtask[];
57
+ metadata: unknown;
58
+ worker_id?: string | null;
59
+ created_by: string;
60
+ created_at: string;
61
+ updated_at: string;
62
+ completed_at?: string | null;
63
+ }
64
+
65
+ export const TASK_STATUS_ORDER: TaskStatus[] = [
66
+ "in_progress",
67
+ "ready",
68
+ "pending_approval",
69
+ "backlog",
70
+ "done",
71
+ ];
72
+
73
+ export const TASK_STATUS_LABEL: Record<TaskStatus, string> = {
74
+ in_progress: "In Progress",
75
+ ready: "Ready",
76
+ pending_approval: "Pending Approval",
77
+ backlog: "Backlog",
78
+ done: "Done",
79
+ };
80
+
81
+ export const TASK_PRIORITY_LABEL: Record<TaskPriority, string> = {
82
+ critical: "Critical",
83
+ high: "High",
84
+ medium: "Medium",
85
+ low: "Low",
86
+ };
87
+
88
+ // Domain objects
89
+ export interface TaskInfo {
90
+ id: string;
91
+ title: string;
92
+ status: string;
93
+ priority: string;
94
+ assignees: string[];
95
+ conversation_id?: string;
96
+ }
97
+
98
+ export interface MemoryInfo {
99
+ id: string;
100
+ type: string;
101
+ content: string;
102
+ source?: string;
103
+ edges?: Array<{ target: string; relation: string }>;
104
+ }
105
+
106
+ export interface CronJobInfo {
107
+ id: string;
108
+ name: string;
109
+ schedule: string;
110
+ last_run?: string;
111
+ next_run?: string;
112
+ status: string;
113
+ }
114
+
115
+ export interface AgentInfo {
116
+ id: string;
117
+ name: string;
118
+ detail: string;
119
+ status?: string;
120
+ }
121
+
122
+ export interface ModelOption {
123
+ id: string;
124
+ name: string;
125
+ provider: string;
126
+ context_window?: number;
127
+ capabilities?: string[];
128
+ }
129
+
130
+ // ---------------------------------------------------------------------------
131
+ // Helpers
132
+ // ---------------------------------------------------------------------------
133
+
134
+ export function tryParseJson(text: string): Record<string, unknown> | null {
135
+ if (!text || text.trim().length === 0) return null;
136
+ try {
137
+ const parsed = JSON.parse(text);
138
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
139
+ return parsed as Record<string, unknown>;
140
+ }
141
+ return null;
142
+ } catch {
143
+ return null;
144
+ }
145
+ }
146
+
147
+ export function isErrorResult(text: string, parsed: Record<string, unknown> | null): boolean {
148
+ if (parsed?.error) return true;
149
+ if (parsed?.status === "error") return true;
150
+ if (parsed?.success === false) return true;
151
+ if (typeof parsed?.exit_code === "number" && parsed.exit_code !== 0) return true;
152
+ const lower = text.toLowerCase();
153
+ return (
154
+ lower.startsWith("error:") ||
155
+ lower.startsWith("error -") ||
156
+ lower.startsWith("failed:") ||
157
+ lower.startsWith("toolset error:")
158
+ );
159
+ }
160
+
161
+ // ---------------------------------------------------------------------------
162
+ // Transcript → TranscriptItem[] pairing
163
+ // ---------------------------------------------------------------------------
164
+
165
+ /**
166
+ * Walk a flat TranscriptStep[] and pair each tool_call with its tool_result
167
+ * via call_id matching, plus emit standalone text steps. Returns an ordered
168
+ * list of renderable items: text blocks and paired tool calls.
169
+ */
170
+ export function pairTranscriptSteps(steps: TranscriptStep[]): TranscriptItem[] {
171
+ const items: TranscriptItem[] = [];
172
+ const resultsById = new Map<string, { name: string; text: string }>();
173
+
174
+ // First pass: index all tool_result steps by call_id
175
+ for (const step of steps) {
176
+ if (step.type === "tool_result") {
177
+ resultsById.set(step.call_id, { name: step.name, text: step.text });
178
+ }
179
+ }
180
+
181
+ // Second pass: emit items in order
182
+ for (const step of steps) {
183
+ if (step.type === "action") {
184
+ for (const content of step.content) {
185
+ if (content.type === "text") {
186
+ items.push({ kind: "text", text: content.text });
187
+ } else if (content.type === "tool_call") {
188
+ const result = resultsById.get(content.id);
189
+ const parsedArgs = tryParseJson(content.args);
190
+ const parsedResult = result ? tryParseJson(result.text) : null;
191
+
192
+ const hasError = result ? isErrorResult(result.text, parsedResult) : false;
193
+
194
+ items.push({
195
+ kind: "tool",
196
+ pair: {
197
+ id: content.id,
198
+ name: content.name,
199
+ argsRaw: content.args,
200
+ args: parsedArgs,
201
+ resultRaw: result?.text ?? null,
202
+ result: parsedResult,
203
+ status: result ? (hasError ? "error" : "completed") : "running",
204
+ },
205
+ });
206
+ }
207
+ }
208
+ }
209
+ }
210
+
211
+ return items;
212
+ }
@@ -0,0 +1,126 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // React hook over `streamChatCompletion`: one conversation streamed from an OpenAI-compatible
3
+ // endpoint, with stop, reset and the `unavailable` status of a model that cannot answer.
4
+
5
+ "use client";
6
+
7
+ import { useCallback, useEffect, useReducer, useRef } from "react";
8
+
9
+ import {
10
+ type ChatMessage,
11
+ type ChatStatus,
12
+ ChatStreamError,
13
+ chatReducer,
14
+ initialChatState,
15
+ newMessageId,
16
+ streamChatCompletion,
17
+ } from "./openaiChatStream";
18
+
19
+ export interface UseOpenAIChatOptions {
20
+ /** API root including `/v1` (e.g. `http://127.0.0.1:8080/v1`). */
21
+ baseUrl: string;
22
+ model?: string;
23
+ fetch?: typeof fetch;
24
+ headers?: Record<string, string>;
25
+ initialMessages?: ChatMessage[];
26
+ /** Messages sent before the conversation on every request (system prompt...). */
27
+ preamble?: ChatMessage[];
28
+ /** Extra body fields (`temperature`, `tools`...). */
29
+ body?: Record<string, unknown>;
30
+ /** Called with the finished assistant message. */
31
+ onFinish?: (message: ChatMessage) => void;
32
+ onError?: (error: ChatStreamError) => void;
33
+ }
34
+
35
+ export interface UseOpenAIChatResult {
36
+ messages: ChatMessage[];
37
+ status: ChatStatus;
38
+ error: ChatStreamError | null;
39
+ /** Send a user message and stream the answer. Ignored while streaming. */
40
+ send: (content: string) => Promise<void>;
41
+ /** Abort the running request; the partial answer stays. */
42
+ stop: () => void;
43
+ /** Abort and clear (or replace) the conversation. */
44
+ reset: (messages?: ChatMessage[]) => void;
45
+ setMessages: (messages: ChatMessage[]) => void;
46
+ }
47
+
48
+ export function useOpenAIChat(options: UseOpenAIChatOptions): UseOpenAIChatResult {
49
+ const [state, dispatch] = useReducer(chatReducer, options.initialMessages, initialChatState);
50
+ const controller = useRef<AbortController | null>(null);
51
+ const latest = useRef({ options, state });
52
+
53
+ useEffect(() => {
54
+ latest.current = { options, state };
55
+ });
56
+ useEffect(() => () => controller.current?.abort(), []);
57
+
58
+ const send = useCallback(async (content: string) => {
59
+ const { options: o, state: s } = latest.current;
60
+ if (s.status === "streaming" || controller.current || !content.trim()) return;
61
+ const user: ChatMessage = { id: newMessageId(), role: "user", content };
62
+ const assistantId = newMessageId();
63
+ const abort = new AbortController();
64
+ controller.current = abort;
65
+ dispatch({ type: "start", user, assistantId });
66
+ let assistant: ChatMessage = { id: assistantId, role: "assistant", content: "" };
67
+ try {
68
+ for await (const event of streamChatCompletion({
69
+ baseUrl: o.baseUrl,
70
+ model: o.model,
71
+ messages: [...(o.preamble ?? []), ...s.messages, user],
72
+ body: o.body,
73
+ headers: o.headers,
74
+ fetch: o.fetch,
75
+ signal: abort.signal,
76
+ })) {
77
+ dispatch({ type: "event", assistantId, event });
78
+ assistant = chatReducer(
79
+ { messages: [assistant], status: "streaming", error: null },
80
+ { type: "event", assistantId, event },
81
+ ).messages[0]!;
82
+ }
83
+ dispatch({ type: "finish" });
84
+ o.onFinish?.(assistant);
85
+ } catch (cause) {
86
+ const error =
87
+ cause instanceof ChatStreamError
88
+ ? cause
89
+ : new ChatStreamError("stream", String(cause), { cause });
90
+ if (error.kind === "aborted" || abort.signal.aborted) {
91
+ dispatch({ type: "stop" });
92
+ } else {
93
+ dispatch({ type: "fail", error });
94
+ o.onError?.(error);
95
+ }
96
+ } finally {
97
+ if (controller.current === abort) controller.current = null;
98
+ }
99
+ }, []);
100
+
101
+ const stop = useCallback(() => {
102
+ controller.current?.abort();
103
+ controller.current = null;
104
+ dispatch({ type: "stop" });
105
+ }, []);
106
+
107
+ const reset = useCallback((messages?: ChatMessage[]) => {
108
+ controller.current?.abort();
109
+ controller.current = null;
110
+ dispatch({ type: "reset", messages });
111
+ }, []);
112
+
113
+ const setMessages = useCallback((messages: ChatMessage[]) => {
114
+ dispatch({ type: "set", messages });
115
+ }, []);
116
+
117
+ return {
118
+ messages: state.messages,
119
+ status: state.status,
120
+ error: state.error,
121
+ send,
122
+ stop,
123
+ reset,
124
+ setMessages,
125
+ };
126
+ }