thinyai 0.1.1 → 0.1.2

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 (2) hide show
  1. package/dist/bin.js +53 -35
  2. package/package.json +6 -6
package/dist/bin.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/main.ts
4
4
  import { createInterface } from "readline/promises";
5
- import { clearLine, cursorTo } from "readline";
5
+ import { clearLine, cursorTo, emitKeypressEvents } from "readline";
6
6
  import { stdin, stdout } from "process";
7
7
  import { mkdirSync } from "fs";
8
8
  import { homedir } from "os";
@@ -452,9 +452,9 @@ async function createAgent(config) {
452
452
  const seed = config.systemPrompt && !history.some((m) => m.role === "system") ? [systemMessage(config.systemPrompt), ...history] : history;
453
453
  const generate = composeModel(extensions.middleware.model, async (req) => {
454
454
  if (opts.onToken && config.model.stream) {
455
- return assembleStream(config.model.stream(req.messages, req.tools), opts.onToken);
455
+ return assembleStream(config.model.stream(req.messages, req.tools, opts.signal), opts.onToken);
456
456
  }
457
- return config.model.generate(req.messages, req.tools);
457
+ return config.model.generate(req.messages, req.tools, opts.signal);
458
458
  });
459
459
  const runComposedTool = composeTool(
460
460
  extensions.middleware.tool,
@@ -829,44 +829,38 @@ function buildToolOptions(tools) {
829
829
  if (tools.length === 0) return { tools: void 0, toolChoice: void 0 };
830
830
  return { tools: toAiTools(tools), toolChoice: "auto" };
831
831
  }
832
+ var KNOWN_PROVIDERS = /* @__PURE__ */ new Set(["openai", "openai-compat", "anthropic"]);
832
833
  function resolveModel(model, opts) {
833
834
  if (typeof model !== "string") return model;
834
835
  const colonIdx = model.indexOf(":");
835
- if (colonIdx === -1) {
836
- if (opts.anthropic?.baseURL) {
837
- return createAnthropic({ baseURL: opts.anthropic.baseURL, apiKey: opts.anthropic.apiKey })(
838
- model
836
+ const prefix = colonIdx === -1 ? "" : model.slice(0, colonIdx);
837
+ if (KNOWN_PROVIDERS.has(prefix)) {
838
+ const modelId = model.slice(colonIdx + 1);
839
+ if (prefix === "anthropic") {
840
+ return createAnthropic({ baseURL: opts.anthropic?.baseURL, apiKey: opts.anthropic?.apiKey })(
841
+ modelId
839
842
  );
840
843
  }
841
- return createOpenAI({ baseURL: opts.openai?.baseURL, apiKey: opts.openai?.apiKey })(model);
842
- }
843
- const provider = model.slice(0, colonIdx);
844
- const modelId = model.slice(colonIdx + 1);
845
- if (provider === "openai" || provider === "openai-compat") {
846
844
  return createOpenAI({ baseURL: opts.openai?.baseURL, apiKey: opts.openai?.apiKey })(modelId);
847
845
  }
848
- if (provider === "anthropic") {
849
- return createAnthropic({ baseURL: opts.anthropic?.baseURL, apiKey: opts.anthropic?.apiKey })(
850
- modelId
846
+ if (opts.anthropic?.baseURL) {
847
+ return createAnthropic({ baseURL: opts.anthropic.baseURL, apiKey: opts.anthropic.apiKey })(
848
+ model
851
849
  );
852
850
  }
853
- throw new Error(
854
- `unknown provider "${provider}" in model string "${model}"
855
- Supported prefixes: "openai:<id>", "openai-compat:<id>", "anthropic:<id>"
856
- Or omit the prefix and set THINY_OPENAI_BASE_URL / THINY_ANTHROPIC_BASE_URL instead.
857
- Or pass a LanguageModel instance directly.`
858
- );
851
+ return createOpenAI({ baseURL: opts.openai?.baseURL, apiKey: opts.openai?.apiKey })(model);
859
852
  }
860
853
  function aiSdkModel(opts) {
861
854
  const model = resolveModel(opts.model, opts);
862
855
  const maxRetries = opts.maxRetries ?? 2;
863
856
  return {
864
- async generate(messages, tools) {
857
+ async generate(messages, tools, signal) {
865
858
  const result = await generateText({
866
859
  model,
867
860
  messages: toCoreMessages(messages),
868
861
  ...buildToolOptions(tools),
869
- maxRetries
862
+ maxRetries,
863
+ abortSignal: signal
870
864
  });
871
865
  return {
872
866
  text: result.text || void 0,
@@ -880,12 +874,13 @@ function aiSdkModel(opts) {
880
874
  usage: normalizeUsage(result.usage)
881
875
  };
882
876
  },
883
- async *stream(messages, tools) {
877
+ async *stream(messages, tools, signal) {
884
878
  const result = streamText({
885
879
  model,
886
880
  messages: toCoreMessages(messages),
887
881
  ...buildToolOptions(tools),
888
- maxRetries
882
+ maxRetries,
883
+ abortSignal: signal
889
884
  });
890
885
  for await (const part of result.fullStream) {
891
886
  if (part.type === "text-delta") {
@@ -956,7 +951,7 @@ function pinoLogger(opts = {}) {
956
951
  );
957
952
  }
958
953
  if (opts.file) {
959
- const destination = pino2.destination({ dest: opts.file, sync: false });
954
+ const destination = pino2.destination({ dest: opts.file, sync: true });
960
955
  return adaptPinoLogger(pino2(pinoConfig(level), destination));
961
956
  }
962
957
  const usePretty = opts.pretty ?? (opts.file === void 0 && process.env.NODE_ENV !== "production");
@@ -1827,7 +1822,9 @@ async function runCli() {
1827
1822
  namespace: process.env.MEMWAL_NAMESPACE ?? userId
1828
1823
  }) : walrusMemoryPlugin({
1829
1824
  client: walrus,
1830
- pointers: filePointerStore(process.env.WALRUS_POINTERS ?? "thiny-pointers.json"),
1825
+ // Stable per-user location (~/.thiny) so cross-session memory works no matter which
1826
+ // directory `thiny` is launched from — a cwd-relative file would fragment per folder.
1827
+ pointers: filePointerStore(process.env.WALRUS_POINTERS ?? join(thinyDir, "thiny-pointers.json")),
1831
1828
  userId,
1832
1829
  onStoreStart: () => pendingWrites += 1,
1833
1830
  onStore: (ref) => {
@@ -1899,6 +1896,7 @@ async function runCli() {
1899
1896
  if (walrusAudit)
1900
1897
  renderInfo(`Walrus audit: ON (${network}) \u2014 each turn's action log is stored + verifiable`);
1901
1898
  const rl = createInterface({ input: stdin, output: stdout });
1899
+ emitKeypressEvents(stdin);
1902
1900
  const spinner = new Spinner();
1903
1901
  const flushMemory = memoryPlugin.flush;
1904
1902
  const PROMPT = "\x1B[36mYou\x1B[0m \x1B[2m\u203A\x1B[0m ";
@@ -2011,10 +2009,15 @@ Audit trail ${blobId}
2011
2009
  continue;
2012
2010
  }
2013
2011
  renderAgentLabel(personaName);
2014
- spinner.start("thinking\u2026");
2012
+ spinner.start("thinking\u2026 (esc to cancel)");
2015
2013
  budget.reset();
2016
2014
  resetTurn(turn);
2017
2015
  const startedAt = Date.now();
2016
+ const ac = new AbortController();
2017
+ const onKey = (_s, key) => {
2018
+ if (key?.name === "escape") ac.abort();
2019
+ };
2020
+ stdin.on("keypress", onKey);
2018
2021
  try {
2019
2022
  let firstToken = true;
2020
2023
  const stream = createMarkdownWriter((s) => stdout.write(s));
@@ -2026,15 +2029,28 @@ Audit trail ${blobId}
2026
2029
  spinner.start("running\u2026");
2027
2030
  };
2028
2031
  agent.events.on("beforeToolCall", toolHandler);
2029
- const reply = await agent.run(trimmed, {
2030
- sessionId: currentSessionId,
2031
- onToken: (delta) => {
2032
+ let reply;
2033
+ try {
2034
+ reply = await agent.run(trimmed, {
2035
+ sessionId: currentSessionId,
2036
+ signal: ac.signal,
2037
+ onToken: (delta) => {
2038
+ spinner.stop();
2039
+ firstToken = false;
2040
+ stream.push(delta);
2041
+ }
2042
+ });
2043
+ } catch (err) {
2044
+ if (ac.signal.aborted) {
2032
2045
  spinner.stop();
2033
- firstToken = false;
2034
- stream.push(delta);
2046
+ stream.end();
2047
+ stdout.write("\n \x1B[2m\u2298 cancelled (Esc)\x1B[0m\n");
2048
+ continue;
2035
2049
  }
2036
- });
2037
- agent.events.off("beforeToolCall", toolHandler);
2050
+ throw err;
2051
+ } finally {
2052
+ agent.events.off("beforeToolCall", toolHandler);
2053
+ }
2038
2054
  spinner.stop();
2039
2055
  if (firstToken) {
2040
2056
  stream.push(reply.length > 0 ? reply : "\x1B[2m(model returned empty response)\x1B[0m");
@@ -2079,6 +2095,8 @@ Audit trail ${blobId}
2079
2095
  looksLikeModelError ? `${msg}
2080
2096
  \u21B3 Check your model, base URL, and API key (run \`thiny init\`, or edit ~/.thiny/config.json / .env).` : msg
2081
2097
  );
2098
+ } finally {
2099
+ stdin.off("keypress", onKey);
2082
2100
  }
2083
2101
  }
2084
2102
  } finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinyai",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Thiny AI — a beautiful terminal agent: interactive chat, tools, Walrus memory, and Sui execution.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -36,14 +36,14 @@
36
36
  "devDependencies": {
37
37
  "tsup": "^8.5.1",
38
38
  "typescript": "^5.5.0",
39
- "@thiny/mcp": "0.1.0",
40
- "@thiny/core": "0.1.0",
39
+ "@thiny/logger-pino": "0.1.0",
41
40
  "@thiny/model-aisdk": "0.1.0",
41
+ "@thiny/core": "0.1.0",
42
42
  "@thiny/memory-memwal": "0.1.0",
43
- "@thiny/logger-pino": "0.1.0",
44
- "@thiny/walrus": "0.1.0",
43
+ "@thiny/mcp": "0.1.0",
45
44
  "@thiny/plugin-agents": "0.1.0",
46
- "@thiny/skills": "0.1.0"
45
+ "@thiny/skills": "0.1.0",
46
+ "@thiny/walrus": "0.1.0"
47
47
  },
48
48
  "author": "Thiny AI",
49
49
  "engines": {