echospace 0.1.0-alpha.1 → 0.1.0-alpha.3

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.
@@ -1,13 +1,14 @@
1
1
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="131 131 250 250" width="512" height="512">
2
- <!-- Outer brown rounded square -->
3
- <rect x="139.3" y="139.3" width="233.3" height="233.3" rx="36.7" ry="36.7" fill="#BB4B03"/>
4
-
5
- <!-- Inner cream cutout -->
6
- <rect x="168" y="168" width="176" height="176" rx="25" ry="25" fill="#F2E2C1"/>
7
-
8
- <!-- Middle brown rounded square -->
9
- <rect x="198" y="198" width="116" height="116" rx="18.3" ry="18.3" fill="#BB4B03"/>
10
-
11
- <!-- Center cream square -->
12
- <rect x="226.3" y="226.3" width="59.3" height="59.3" rx="9.3" ry="9.3" fill="#F2E2C1"/>
2
+ <defs>
3
+ <mask id="cutout">
4
+ <rect x="131" y="131" width="250" height="250" fill="white"/>
5
+ <!-- Inner cutout -->
6
+ <rect x="168" y="168" width="176" height="176" rx="25" ry="25" fill="black"/>
7
+ <!-- Middle fill-back -->
8
+ <rect x="198" y="198" width="116" height="116" rx="18.3" ry="18.3" fill="white"/>
9
+ <!-- Center cutout -->
10
+ <rect x="226.3" y="226.3" width="59.3" height="59.3" rx="9.3" ry="9.3" fill="black"/>
11
+ </mask>
12
+ </defs>
13
+ <rect x="139.3" y="139.3" width="233.3" height="233.3" rx="36.7" ry="36.7" fill="currentColor" mask="url(#cutout)"/>
13
14
  </svg>
@@ -1,20 +1,14 @@
1
1
  <!doctype html>
2
- <html lang="en">
2
+ <html lang="en" style="overscroll-behavior: none">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>EchoSpace</title>
7
7
  <link rel="icon" type="image/svg+xml" href="/echospace-logo.svg">
8
- <link rel="preconnect" href="https://fonts.googleapis.com" />
9
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10
- <link
11
- href="https://fonts.googleapis.com/css2?family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;1,9..40,400&family=IBM+Plex+Mono:wght@400;500&display=swap"
12
- rel="stylesheet"
13
- />
14
- <script type="module" crossorigin src="/assets/index-C3HpfhnB.js"></script>
15
- <link rel="stylesheet" crossorigin href="/assets/index-kxAuiugv.css">
8
+ <script type="module" crossorigin src="/assets/index-ja1djPyy.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-gZkfzHQ0.css">
16
10
  </head>
17
- <body>
11
+ <body style="width: 100%; height: 100%; margin: 0; overflow: hidden">
18
12
  <div id="root"></div>
19
13
  </body>
20
14
  </html>
@@ -0,0 +1,110 @@
1
+ // src/core/echo/parser.ts
2
+ import { nanoid } from "nanoid";
3
+ function parseEcho(raw) {
4
+ const lines = raw.split("\n").filter((line) => line.trim().length > 0);
5
+ if (lines.length === 0) {
6
+ throw new Error("Empty .echo file");
7
+ }
8
+ const records = lines.map((line, i) => {
9
+ try {
10
+ return JSON.parse(line);
11
+ } catch {
12
+ throw new Error(`Invalid JSON on line ${i + 1}`);
13
+ }
14
+ });
15
+ const metaRecord = records[0];
16
+ if (!metaRecord || metaRecord.kind !== "meta") {
17
+ throw new Error("First line must be a meta record");
18
+ }
19
+ const messages = records.filter((r) => r.kind === "message").map(normalizeMessage);
20
+ return {
21
+ meta: metaRecord,
22
+ messages
23
+ };
24
+ }
25
+ function normalizeMessage(message) {
26
+ const raw = message;
27
+ let msg = message;
28
+ if (!msg.id) {
29
+ msg = { ...msg, id: nanoid(8) };
30
+ }
31
+ if (!msg.created_at && (raw.ts || raw.timestamp)) {
32
+ msg = { ...msg, created_at: raw.ts ?? raw.timestamp };
33
+ }
34
+ const needsPartNormalization = msg.parts.some((p) => {
35
+ if (p.type !== "tool_result") return false;
36
+ const r = p;
37
+ return !r.tool_call_id && (r.id || r.tool_use_id);
38
+ });
39
+ if (needsPartNormalization) {
40
+ msg = {
41
+ ...msg,
42
+ parts: msg.parts.map((p) => {
43
+ if (p.type !== "tool_result") return p;
44
+ const r = p;
45
+ if (!r.tool_call_id && (r.id || r.tool_use_id)) {
46
+ const { id, tool_use_id, ...rest } = r;
47
+ return { ...rest, tool_call_id: id ?? tool_use_id };
48
+ }
49
+ return p;
50
+ })
51
+ };
52
+ }
53
+ return msg;
54
+ }
55
+ function serializeEcho(conversation) {
56
+ const records = [conversation.meta, ...conversation.messages];
57
+ return records.map((r) => JSON.stringify(r)).join("\n") + "\n";
58
+ }
59
+ function createConversation(id, title) {
60
+ return {
61
+ meta: {
62
+ kind: "meta",
63
+ v: 1,
64
+ id,
65
+ title,
66
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
67
+ },
68
+ messages: []
69
+ };
70
+ }
71
+ function getMessageText(message) {
72
+ return message.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
73
+ }
74
+ function withUpdatedMeta(conversation, updates) {
75
+ return {
76
+ ...conversation,
77
+ meta: { ...conversation.meta, ...updates }
78
+ };
79
+ }
80
+ function withAppendedMessages(conversation, messages) {
81
+ return {
82
+ ...conversation,
83
+ messages: [...conversation.messages, ...messages]
84
+ };
85
+ }
86
+ function withUpdatedMessage(conversation, messageId, updates) {
87
+ return {
88
+ ...conversation,
89
+ messages: conversation.messages.map(
90
+ (m) => m.id === messageId ? { ...m, ...updates } : m
91
+ )
92
+ };
93
+ }
94
+ function withRemovedMessage(conversation, messageId) {
95
+ return {
96
+ ...conversation,
97
+ messages: conversation.messages.filter((m) => m.id !== messageId)
98
+ };
99
+ }
100
+
101
+ export {
102
+ parseEcho,
103
+ serializeEcho,
104
+ createConversation,
105
+ getMessageText,
106
+ withUpdatedMeta,
107
+ withAppendedMessages,
108
+ withUpdatedMessage,
109
+ withRemovedMessage
110
+ };
@@ -1,5 +1,5 @@
1
- import { E as EchoConversation, a as EchoMessage, b as EchoMeta } from '../types-CYpEmXbp.js';
2
- export { c as EchoPart, d as EchoRecord, e as EchoRole, f as EchoSettings, g as EchoToolDefinition, I as ImagePart, T as TextPart, h as ThinkingPart, i as ToolCallPart, j as ToolResultPart } from '../types-CYpEmXbp.js';
1
+ import { E as EchoConversation, a as EchoMessage, b as EchoMeta } from '../types-DonjLc45.js';
2
+ export { c as EchoPart, d as EchoRecord, e as EchoRole, f as EchoSettings, g as EchoToolDefinition, I as ImagePart, T as TextPart, h as ThinkingPart, i as ToolCallPart, j as ToolResultPart } from '../types-DonjLc45.js';
3
3
 
4
4
  /**
5
5
  * Parse an .echo NDJSON string into an EchoConversation.
@@ -7,7 +7,7 @@ import {
7
7
  withRemovedMessage,
8
8
  withUpdatedMessage,
9
9
  withUpdatedMeta
10
- } from "../chunk-U4VEJP3N.js";
10
+ } from "../chunk-LGVA3Y5G.js";
11
11
  export {
12
12
  createConversation,
13
13
  getMessageText,
@@ -1,4 +1,4 @@
1
- import { a as EchoMessage, f as EchoSettings, c as EchoPart } from '../types-CYpEmXbp.js';
1
+ import { a as EchoMessage, f as EchoSettings, c as EchoPart } from '../types-DonjLc45.js';
2
2
 
3
3
  /**
4
4
  * Provider configuration from config.yaml.
@@ -20,7 +20,7 @@ function toAnthropicContent(msg) {
20
20
  case "tool_result":
21
21
  blocks.push({
22
22
  type: "tool_result",
23
- tool_use_id: part.id,
23
+ tool_use_id: part.tool_call_id,
24
24
  content: typeof part.output === "string" ? part.output : JSON.stringify(part.output),
25
25
  is_error: part.is_error
26
26
  });
@@ -148,7 +148,7 @@ function toGeminiContent(msg) {
148
148
  case "tool_result":
149
149
  parts.push({
150
150
  functionResponse: {
151
- name: part.id,
151
+ name: part.tool_call_id,
152
152
  response: { result: part.output }
153
153
  }
154
154
  });
@@ -255,7 +255,7 @@ function toOpenAIMessage(msg) {
255
255
  if (msg.role === "tool" && toolResults.length > 0) {
256
256
  const tr = toolResults[0];
257
257
  result.content = typeof tr.output === "string" ? tr.output : JSON.stringify(tr.output);
258
- result.tool_call_id = tr.id;
258
+ result.tool_call_id = tr.tool_call_id;
259
259
  return result;
260
260
  }
261
261
  if (msg.role === "assistant") {
@@ -1,4 +1,4 @@
1
- import { a as EchoMessage } from '../types-CYpEmXbp.js';
1
+ import { a as EchoMessage } from '../types-DonjLc45.js';
2
2
 
3
3
  type DetectedFormat = "openai" | "anthropic" | "google" | "vercel" | "echo" | "raw" | "unknown";
4
4
  /**
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseEcho
3
- } from "../chunk-U4VEJP3N.js";
3
+ } from "../chunk-LGVA3Y5G.js";
4
4
 
5
5
  // src/core/smart-paste/detector.ts
6
6
  function detectFormat(input) {
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Echo format types — the universal .echo message protocol.
3
+ *
4
+ * An .echo file is NDJSON (newline-delimited JSON).
5
+ * Line 1 is always an EchoMeta record.
6
+ * Subsequent lines are EchoMessage records.
7
+ */
8
+ interface TextPart {
9
+ type: "text";
10
+ text: string;
11
+ }
12
+ interface ThinkingPart {
13
+ type: "thinking";
14
+ text: string;
15
+ }
16
+ interface ToolCallPart {
17
+ type: "tool_call";
18
+ id: string;
19
+ name: string;
20
+ input: unknown;
21
+ }
22
+ interface ToolResultPart {
23
+ type: "tool_result";
24
+ tool_call_id: string;
25
+ output: unknown;
26
+ is_error?: boolean;
27
+ }
28
+ interface ImagePart {
29
+ type: "image";
30
+ url?: string;
31
+ base64?: string;
32
+ media_type?: string;
33
+ }
34
+ type EchoPart = TextPart | ThinkingPart | ToolCallPart | ToolResultPart | ImagePart;
35
+ interface EchoToolDefinition {
36
+ name: string;
37
+ description?: string;
38
+ parameters: Record<string, unknown>;
39
+ strict?: boolean;
40
+ }
41
+ interface EchoSettings {
42
+ provider?: string;
43
+ model?: string;
44
+ temperature?: number;
45
+ max_tokens?: number;
46
+ top_p?: number;
47
+ response_format?: "text" | "json_object" | "json_schema";
48
+ json_schema?: {
49
+ name: string;
50
+ schema: Record<string, unknown>;
51
+ strict?: boolean;
52
+ };
53
+ tools?: EchoToolDefinition[];
54
+ }
55
+ type EchoRole = "system" | "user" | "assistant" | "tool";
56
+ interface EchoMeta {
57
+ kind: "meta";
58
+ v: 1;
59
+ id: string;
60
+ title?: string;
61
+ created_at: string;
62
+ settings?: EchoSettings;
63
+ }
64
+ interface EchoMessage {
65
+ kind: "message";
66
+ id: string;
67
+ role: EchoRole;
68
+ created_at: string;
69
+ parts: EchoPart[];
70
+ meta?: {
71
+ provider?: string;
72
+ model?: string;
73
+ usage?: {
74
+ input_tokens?: number;
75
+ output_tokens?: number;
76
+ };
77
+ latency?: {
78
+ duration?: number;
79
+ ttft?: number;
80
+ };
81
+ };
82
+ }
83
+ type EchoRecord = EchoMeta | EchoMessage;
84
+ interface EchoConversation {
85
+ meta: EchoMeta;
86
+ messages: EchoMessage[];
87
+ }
88
+
89
+ export type { EchoConversation as E, ImagePart as I, TextPart as T, EchoMessage as a, EchoMeta as b, EchoPart as c, EchoRecord as d, EchoRole as e, EchoSettings as f, EchoToolDefinition as g, ThinkingPart as h, ToolCallPart as i, ToolResultPart as j };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "echospace",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "0.1.0-alpha.3",
4
4
  "type": "module",
5
5
  "description": "Open-source, local-first prompt debugging workspace for LLM developers",
6
6
  "license": "MIT",
@@ -56,8 +56,10 @@
56
56
  "format": "prettier --write ."
57
57
  },
58
58
  "dependencies": {
59
+ "@clack/prompts": "^1.1.0",
59
60
  "@hono/node-server": "^1.13.8",
60
61
  "commander": "^13.1.0",
62
+ "geist": "^1.7.0",
61
63
  "get-port": "^7.1.0",
62
64
  "hono": "^4.7.4",
63
65
  "js-yaml": "^4.1.0",