openshain 0.8.0 → 0.9.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/dist/bin.js CHANGED
@@ -7,6 +7,7 @@ import { init } from "./commands/init.js";
7
7
  import { knowledgeBuild, knowledgeCheck } from "./commands/knowledge.js";
8
8
  import { knowledgeAdd } from "./commands/knowledge-add.js";
9
9
  import { mcp } from "./commands/mcp.js";
10
+ import { observeCommand } from "./commands/observe.js";
10
11
  import { principalCheck } from "./commands/principal.js";
11
12
  import { toolsList } from "./commands/tools.js";
12
13
  import { workList, workShow } from "./commands/work.js";
@@ -24,8 +25,13 @@ const USAGE = `使い方:
24
25
  openshain knowledge check 同じ検証を、索引を書かずに行う(--stale で古い資料も報告)
25
26
  openshain knowledge add 決まりを 1 件、質問に答えて追加する
26
27
  openshain principal check <id> その人の社員エージェントがどこで働き、何ができるかを確かめる
28
+ openshain observe <type> 起きたことを記録する。当たる義務があれば Work を作る
27
29
  openshain mcp MCP Server を stdio で起動する
28
30
 
31
+ --source <name> observe で、それをどこから知ったか(既定は cli)
32
+ --at <時刻> observe で、それが起きた時刻(既定はいま)
33
+ --ref <path> observe で、中身の置き場
34
+
29
35
  --principal <id> この端末が誰として働くか。principals/ に書いた id
30
36
  環境変数 OPENSHAIN_PRINCIPAL でも指定できる。本人確認はしない
31
37
  --workspace <dir> 起点のディレクトリ。省略時はカレントディレクトリ
@@ -58,6 +64,9 @@ async function main(argv) {
58
64
  principal: { type: "string" },
59
65
  help: { type: "boolean", short: "h" },
60
66
  stale: { type: "boolean" },
67
+ source: { type: "string" },
68
+ at: { type: "string" },
69
+ ref: { type: "string" },
61
70
  },
62
71
  allowPositionals: true,
63
72
  }));
@@ -118,6 +127,23 @@ async function main(argv) {
118
127
  const workspaceRoot = await findWorkspace(values.workspace ?? process.cwd());
119
128
  return await principalCheck({ workspaceRoot, id, write });
120
129
  }
130
+ case "observe": {
131
+ const type = rest[0];
132
+ if (!type) {
133
+ write(USAGE);
134
+ return 2;
135
+ }
136
+ const workspaceRoot = await findWorkspace(values.workspace ?? process.cwd());
137
+ return await observeCommand({
138
+ workspaceRoot,
139
+ type,
140
+ source: values.source ?? "cli",
141
+ ...(values.at !== undefined && { at: values.at }),
142
+ ...(values.ref !== undefined && { ref: values.ref }),
143
+ as: actingFor(values.principal),
144
+ write,
145
+ });
146
+ }
121
147
  case "work": {
122
148
  const sub = rest[0];
123
149
  const id = rest[1] ?? "";
@@ -0,0 +1,18 @@
1
+ export interface ObserveOptions {
2
+ workspaceRoot: string;
3
+ /** What happened, in the company's own word. An obligation names the same one. */
4
+ type: string;
5
+ source: string;
6
+ /** When it happened, if that is earlier than now. */
7
+ at?: string | undefined;
8
+ /** Where the thing itself is, inside the company folder. */
9
+ ref?: string | undefined;
10
+ as?: string | undefined;
11
+ write: (line: string) => void;
12
+ }
13
+ /**
14
+ * Writes down something that happened, and starts whatever the company said should follow. The
15
+ * source of the fact is whatever ran this: a script watching a mailbox, a folder, or a person
16
+ * typing it. openshain records that it happened, and the obligations decide what that means.
17
+ */
18
+ export declare function observeCommand(options: ObserveOptions): Promise<number>;
@@ -0,0 +1,25 @@
1
+ import { loadConfig, observe } from "@openshain/core";
2
+ /**
3
+ * Writes down something that happened, and starts whatever the company said should follow. The
4
+ * source of the fact is whatever ran this: a script watching a mailbox, a folder, or a person
5
+ * typing it. openshain records that it happened, and the obligations decide what that means.
6
+ */
7
+ export async function observeCommand(options) {
8
+ const { workspaceRoot, type, source, at, ref, as, write } = options;
9
+ const config = await loadConfig(workspaceRoot, { ...(as !== undefined && { as }) });
10
+ const { observation, works } = await observe(workspaceRoot, config.principal.id, {
11
+ type,
12
+ source,
13
+ scope: { profession: config.profession.id },
14
+ ...(at !== undefined && { observedAt: at }),
15
+ ...(ref !== undefined && { payloadRef: ref }),
16
+ });
17
+ write(`${observation.id} ${observation.type}(${observation.source})`);
18
+ if (works.length === 0) {
19
+ write(" この観測に当たる義務はありません。記録だけ残しました");
20
+ return 0;
21
+ }
22
+ for (const id of works)
23
+ write(` Work を作りました ${id}`);
24
+ return 0;
25
+ }
@@ -1,9 +1,9 @@
1
1
  import { join } from "node:path";
2
- import { loadConfig, mayReadWork, noSuchWork, PRINCIPALS_DIR_NAME, parseWorkId, pendingApprovals, pendingQuestions, readPrincipals, WorkStore, } from "@openshain/core";
2
+ import { loadConfig, mayReadWork, measureWork, noSuchWork, PRINCIPALS_DIR_NAME, parseWorkId, pendingApprovals, pendingQuestions, readPrincipals, summarizeUsage, WorkStore, } from "@openshain/core";
3
3
  import { describeInput, padDisplay } from "../format.js";
4
4
  import { errorLabel, failureLabel, rejectionLabel, statusLabel } from "../labels.js";
5
5
  import { nextActor } from "../report.js";
6
- import { formatUsage, summarizeUsage } from "../usage.js";
6
+ import { formatUsage } from "../usage.js";
7
7
  /**
8
8
  * The person this terminal works for, out of the people the company has written. Nobody written:
9
9
  * nobody to be, and every work is read, as before there was more than one person.
@@ -52,6 +52,10 @@ export function describeWork(work, events) {
52
52
  ];
53
53
  if (work.agentName)
54
54
  lines.push(`名前 ${work.agentName}(社員エージェント)`);
55
+ // A work nobody asked for says where it came from, so it is not a mystery in the list.
56
+ if (work.observation) {
57
+ lines.push(`きっかけ ${work.observation}${work.obligation ? `(${work.obligation})` : ""}`);
58
+ }
55
59
  if (work.startedAt)
56
60
  lines.push(`開始 ${work.startedAt}`);
57
61
  if (work.completedAt)
@@ -81,9 +85,33 @@ export function describeWork(work, events) {
81
85
  lines.push(line);
82
86
  }
83
87
  lines.push(formatUsage(summarizeUsage(events)));
88
+ lines.push(measureLine(measureWork(work, events)));
84
89
  lines.push(nextActor(work));
85
90
  return lines;
86
91
  }
92
+ /**
93
+ * What the work asked of a person and what the rules stopped, in one line. Only what happened is
94
+ * written: a work nobody was asked about says so, rather than listing three zeros.
95
+ */
96
+ function measureLine(measures) {
97
+ const { intervention, quality, attention, coverage } = measures;
98
+ const asked = [
99
+ intervention.clarification > 0 ? `質問 ${intervention.clarification}` : "",
100
+ intervention.approval > 0 ? `承認 ${intervention.approval}` : "",
101
+ intervention.expertReview > 0 ? `資格者の判断 ${intervention.expertReview}` : "",
102
+ ].filter(Boolean);
103
+ const notes = [
104
+ quality.corrected > 0 ? `資格者が書き換え ${quality.corrected}` : "",
105
+ quality.stoppedByRule > 0 ? `規則が止めた ${quality.stoppedByRule}` : "",
106
+ coverage.seconds !== undefined ? `所要 ${coverage.seconds} 秒` : "",
107
+ ].filter(Boolean);
108
+ const called = attention.calls === 0
109
+ ? coverage.finished
110
+ ? "人を呼ばずに終わりました"
111
+ : "まだ人を呼んでいません"
112
+ : `人を呼んだ回数 ${attention.calls}(${asked.join("、")})`;
113
+ return [called, ...notes].join(" ");
114
+ }
87
115
  /**
88
116
  * How a reviewer is named on the screen. The role of a reviewer is a qualification the company
89
117
  * names (tax-accountant), not one of the roles a person handles, so the word comes first and the
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
+ export { summarizeUsage, type UsageSummary } from "@openshain/core";
1
2
  export { AGENTS_TEMPLATE, CLAUDE_TEMPLATE, CONFIG_TEMPLATE, type InitOptions, init, MCP_TEMPLATE, } from "./commands/init.ts";
2
3
  export { type McpOptions, mcp } from "./commands/mcp.ts";
3
4
  export { type ToolsListOptions, toolsList } from "./commands/tools.ts";
4
5
  export { describeWork, type WorkListOptions, type WorkShowOptions, workList, workShow, } from "./commands/work.ts";
5
6
  export { ERROR_LABELS, errorLabel, FAILURE_LABELS, failureLabel, REJECTION_LABELS, rejectionLabel, STATUS_LABELS, statusLabel, } from "./labels.ts";
6
7
  export { nextActor, progressLine, report } from "./report.ts";
7
- export { formatUsage, summarizeUsage, type UsageSummary } from "./usage.ts";
8
+ export { formatUsage } from "./usage.ts";
8
9
  export { findWorkspace } from "./workspace.ts";
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  // openshain: Reference CLI agent for the openshain runtime
2
+ // The sums live in core, where anything that reads a record can reach them.
3
+ export { summarizeUsage } from "@openshain/core";
2
4
  export { AGENTS_TEMPLATE, CLAUDE_TEMPLATE, CONFIG_TEMPLATE, init, MCP_TEMPLATE, } from "./commands/init.js";
3
5
  export { mcp } from "./commands/mcp.js";
4
6
  export { toolsList } from "./commands/tools.js";
5
7
  export { describeWork, workList, workShow, } from "./commands/work.js";
6
8
  export { ERROR_LABELS, errorLabel, FAILURE_LABELS, failureLabel, REJECTION_LABELS, rejectionLabel, STATUS_LABELS, statusLabel, } from "./labels.js";
7
9
  export { nextActor, progressLine, report } from "./report.js";
8
- export { formatUsage, summarizeUsage } from "./usage.js";
10
+ export { formatUsage } from "./usage.js";
9
11
  export { findWorkspace } from "./workspace.js";
package/dist/labels.js CHANGED
@@ -5,6 +5,7 @@ export const STATUS_LABELS = {
5
5
  waiting_input: "利用者の入力待ち",
6
6
  waiting_approval: "承認待ち",
7
7
  waiting_external: "外部の応答待ち",
8
+ paused: "停止中",
8
9
  completed: "完了",
9
10
  failed: "失敗",
10
11
  cancelled: "取り消し",
package/dist/report.js CHANGED
@@ -1,7 +1,7 @@
1
- import { pendingQuestions, } from "@openshain/core";
1
+ import { pendingQuestions, summarizeUsage, } from "@openshain/core";
2
2
  import { describeInput, truncate } from "./format.js";
3
3
  import { failureLabel, rejectionLabel, statusLabel } from "./labels.js";
4
- import { formatUsage, summarizeUsage } from "./usage.js";
4
+ import { formatUsage } from "./usage.js";
5
5
  /** One line for a tool call, a rejection or a failure; nothing for the other events. `names` maps call ids to tool names. */
6
6
  export function progressLine(event, names) {
7
7
  switch (event.type) {
package/dist/usage.d.ts CHANGED
@@ -1,12 +1,2 @@
1
- import { type AnyEvent } from "@openshain/core";
2
- export interface UsageSummary {
3
- modelCalls: number;
4
- toolCalls: number;
5
- inputTokens: number;
6
- /** The part of inputTokens a prompt cache served. */
7
- cachedInputTokens: number;
8
- outputTokens: number;
9
- }
10
- /** Totals over a work's events: calls and tokens. */
11
- export declare function summarizeUsage(events: AnyEvent[]): UsageSummary;
1
+ import type { UsageSummary } from "@openshain/core";
12
2
  export declare function formatUsage(summary: UsageSummary): string;
package/dist/usage.js CHANGED
@@ -1,28 +1,3 @@
1
- import { countToolCalls } from "@openshain/core";
2
- /** Totals over a work's events: calls and tokens. */
3
- export function summarizeUsage(events) {
4
- const summary = {
5
- modelCalls: 0,
6
- toolCalls: 0,
7
- inputTokens: 0,
8
- cachedInputTokens: 0,
9
- outputTokens: 0,
10
- };
11
- for (const event of events) {
12
- if (event.type === "model.requested")
13
- summary.modelCalls += 1;
14
- if (event.type === "usage.recorded") {
15
- const { payload } = event;
16
- if (payload.kind === "model_inference") {
17
- summary.inputTokens += payload.usage.inputTokens;
18
- summary.cachedInputTokens += payload.usage.cachedInputTokens ?? 0;
19
- summary.outputTokens += payload.usage.outputTokens;
20
- }
21
- }
22
- }
23
- summary.toolCalls = countToolCalls(events);
24
- return summary;
25
- }
26
1
  export function formatUsage(summary) {
27
2
  if (summary.modelCalls === 0 && summary.inputTokens === 0 && summary.outputTokens === 0) {
28
3
  return `model 呼び出し 0 回、Tool 呼び出し ${summary.toolCalls} 回。model の使用量の記録はない`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openshain",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Reference CLI of the openshain agent harness",
5
5
  "keywords": [
6
6
  "openshain",
@@ -49,10 +49,10 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "@modelcontextprotocol/sdk": "1.30.0",
52
- "@openshain/agent": "0.8.0",
53
- "@openshain/core": "0.8.0",
54
- "@openshain/mcp": "0.8.0",
55
- "@openshain/tools": "0.8.0",
52
+ "@openshain/agent": "0.9.0",
53
+ "@openshain/core": "0.9.0",
54
+ "@openshain/mcp": "0.9.0",
55
+ "@openshain/tools": "0.9.0",
56
56
  "ink": "7.1.1",
57
57
  "marked": "18.0.12",
58
58
  "react": "19.2.8"
package/src/bin.ts CHANGED
@@ -7,6 +7,7 @@ import { init } from "./commands/init.ts";
7
7
  import { knowledgeBuild, knowledgeCheck } from "./commands/knowledge.ts";
8
8
  import { knowledgeAdd } from "./commands/knowledge-add.ts";
9
9
  import { mcp } from "./commands/mcp.ts";
10
+ import { observeCommand } from "./commands/observe.ts";
10
11
  import { principalCheck } from "./commands/principal.ts";
11
12
  import { toolsList } from "./commands/tools.ts";
12
13
  import { workList, workShow } from "./commands/work.ts";
@@ -25,8 +26,13 @@ const USAGE = `使い方:
25
26
  openshain knowledge check 同じ検証を、索引を書かずに行う(--stale で古い資料も報告)
26
27
  openshain knowledge add 決まりを 1 件、質問に答えて追加する
27
28
  openshain principal check <id> その人の社員エージェントがどこで働き、何ができるかを確かめる
29
+ openshain observe <type> 起きたことを記録する。当たる義務があれば Work を作る
28
30
  openshain mcp MCP Server を stdio で起動する
29
31
 
32
+ --source <name> observe で、それをどこから知ったか(既定は cli)
33
+ --at <時刻> observe で、それが起きた時刻(既定はいま)
34
+ --ref <path> observe で、中身の置き場
35
+
30
36
  --principal <id> この端末が誰として働くか。principals/ に書いた id
31
37
  環境変数 OPENSHAIN_PRINCIPAL でも指定できる。本人確認はしない
32
38
  --workspace <dir> 起点のディレクトリ。省略時はカレントディレクトリ
@@ -52,7 +58,15 @@ function actingFor(flag: string | undefined): string | undefined {
52
58
 
53
59
  async function main(argv: string[]): Promise<number> {
54
60
  const write = (line: string) => console.log(plain(line));
55
- let values: { workspace?: string; principal?: string; help?: boolean; stale?: boolean };
61
+ let values: {
62
+ workspace?: string;
63
+ principal?: string;
64
+ help?: boolean;
65
+ stale?: boolean;
66
+ source?: string;
67
+ at?: string;
68
+ ref?: string;
69
+ };
56
70
  let positionals: string[];
57
71
  try {
58
72
  ({ values, positionals } = parseArgs({
@@ -62,6 +76,9 @@ async function main(argv: string[]): Promise<number> {
62
76
  principal: { type: "string" },
63
77
  help: { type: "boolean", short: "h" },
64
78
  stale: { type: "boolean" },
79
+ source: { type: "string" },
80
+ at: { type: "string" },
81
+ ref: { type: "string" },
65
82
  },
66
83
  allowPositionals: true,
67
84
  }));
@@ -120,6 +137,23 @@ async function main(argv: string[]): Promise<number> {
120
137
  const workspaceRoot = await findWorkspace(values.workspace ?? process.cwd());
121
138
  return await principalCheck({ workspaceRoot, id, write });
122
139
  }
140
+ case "observe": {
141
+ const type = rest[0];
142
+ if (!type) {
143
+ write(USAGE);
144
+ return 2;
145
+ }
146
+ const workspaceRoot = await findWorkspace(values.workspace ?? process.cwd());
147
+ return await observeCommand({
148
+ workspaceRoot,
149
+ type,
150
+ source: values.source ?? "cli",
151
+ ...(values.at !== undefined && { at: values.at }),
152
+ ...(values.ref !== undefined && { ref: values.ref }),
153
+ as: actingFor(values.principal),
154
+ write,
155
+ });
156
+ }
123
157
  case "work": {
124
158
  const sub = rest[0];
125
159
  const id = rest[1] ?? "";
@@ -0,0 +1,38 @@
1
+ import { loadConfig, observe } from "@openshain/core";
2
+
3
+ export interface ObserveOptions {
4
+ workspaceRoot: string;
5
+ /** What happened, in the company's own word. An obligation names the same one. */
6
+ type: string;
7
+ source: string;
8
+ /** When it happened, if that is earlier than now. */
9
+ at?: string | undefined;
10
+ /** Where the thing itself is, inside the company folder. */
11
+ ref?: string | undefined;
12
+ as?: string | undefined;
13
+ write: (line: string) => void;
14
+ }
15
+
16
+ /**
17
+ * Writes down something that happened, and starts whatever the company said should follow. The
18
+ * source of the fact is whatever ran this: a script watching a mailbox, a folder, or a person
19
+ * typing it. openshain records that it happened, and the obligations decide what that means.
20
+ */
21
+ export async function observeCommand(options: ObserveOptions): Promise<number> {
22
+ const { workspaceRoot, type, source, at, ref, as, write } = options;
23
+ const config = await loadConfig(workspaceRoot, { ...(as !== undefined && { as }) });
24
+ const { observation, works } = await observe(workspaceRoot, config.principal.id, {
25
+ type,
26
+ source,
27
+ scope: { profession: config.profession.id },
28
+ ...(at !== undefined && { observedAt: at }),
29
+ ...(ref !== undefined && { payloadRef: ref }),
30
+ });
31
+ write(`${observation.id} ${observation.type}(${observation.source})`);
32
+ if (works.length === 0) {
33
+ write(" この観測に当たる義務はありません。記録だけ残しました");
34
+ return 0;
35
+ }
36
+ for (const id of works) write(` Work を作りました ${id}`);
37
+ return 0;
38
+ }
@@ -4,6 +4,7 @@ import {
4
4
  type Event,
5
5
  loadConfig,
6
6
  mayReadWork,
7
+ measureWork,
7
8
  noSuchWork,
8
9
  PRINCIPALS_DIR_NAME,
9
10
  type Principal,
@@ -11,13 +12,15 @@ import {
11
12
  pendingApprovals,
12
13
  pendingQuestions,
13
14
  readPrincipals,
15
+ summarizeUsage,
14
16
  type Work,
17
+ type WorkMeasures,
15
18
  WorkStore,
16
19
  } from "@openshain/core";
17
20
  import { describeInput, padDisplay } from "../format.ts";
18
21
  import { errorLabel, failureLabel, rejectionLabel, statusLabel } from "../labels.ts";
19
22
  import { nextActor } from "../report.ts";
20
- import { formatUsage, summarizeUsage } from "../usage.ts";
23
+ import { formatUsage } from "../usage.ts";
21
24
 
22
25
  export interface WorkListOptions {
23
26
  workspaceRoot: string;
@@ -85,6 +88,10 @@ export function describeWork(work: Work, events: AnyEvent[]): string[] {
85
88
  `作成 ${work.createdAt}`,
86
89
  ];
87
90
  if (work.agentName) lines.push(`名前 ${work.agentName}(社員エージェント)`);
91
+ // A work nobody asked for says where it came from, so it is not a mystery in the list.
92
+ if (work.observation) {
93
+ lines.push(`きっかけ ${work.observation}${work.obligation ? `(${work.obligation})` : ""}`);
94
+ }
88
95
  if (work.startedAt) lines.push(`開始 ${work.startedAt}`);
89
96
  if (work.completedAt) lines.push(`終了 ${work.completedAt}`);
90
97
  if (work.outcome) {
@@ -113,10 +120,36 @@ export function describeWork(work: Work, events: AnyEvent[]): string[] {
113
120
  for (const line of calls) lines.push(line);
114
121
  }
115
122
  lines.push(formatUsage(summarizeUsage(events)));
123
+ lines.push(measureLine(measureWork(work, events)));
116
124
  lines.push(nextActor(work));
117
125
  return lines;
118
126
  }
119
127
 
128
+ /**
129
+ * What the work asked of a person and what the rules stopped, in one line. Only what happened is
130
+ * written: a work nobody was asked about says so, rather than listing three zeros.
131
+ */
132
+ function measureLine(measures: WorkMeasures): string {
133
+ const { intervention, quality, attention, coverage } = measures;
134
+ const asked = [
135
+ intervention.clarification > 0 ? `質問 ${intervention.clarification}` : "",
136
+ intervention.approval > 0 ? `承認 ${intervention.approval}` : "",
137
+ intervention.expertReview > 0 ? `資格者の判断 ${intervention.expertReview}` : "",
138
+ ].filter(Boolean);
139
+ const notes = [
140
+ quality.corrected > 0 ? `資格者が書き換え ${quality.corrected}` : "",
141
+ quality.stoppedByRule > 0 ? `規則が止めた ${quality.stoppedByRule}` : "",
142
+ coverage.seconds !== undefined ? `所要 ${coverage.seconds} 秒` : "",
143
+ ].filter(Boolean);
144
+ const called =
145
+ attention.calls === 0
146
+ ? coverage.finished
147
+ ? "人を呼ばずに終わりました"
148
+ : "まだ人を呼んでいません"
149
+ : `人を呼んだ回数 ${attention.calls}(${asked.join("、")})`;
150
+ return [called, ...notes].join(" ");
151
+ }
152
+
120
153
  /**
121
154
  * How a reviewer is named on the screen. The role of a reviewer is a qualification the company
122
155
  * names (tax-accountant), not one of the roles a person handles, so the word comes first and the
package/src/index.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  // openshain: Reference CLI agent for the openshain runtime
2
+
3
+ // The sums live in core, where anything that reads a record can reach them.
4
+ export { summarizeUsage, type UsageSummary } from "@openshain/core";
2
5
  export {
3
6
  AGENTS_TEMPLATE,
4
7
  CLAUDE_TEMPLATE,
@@ -27,5 +30,5 @@ export {
27
30
  statusLabel,
28
31
  } from "./labels.ts";
29
32
  export { nextActor, progressLine, report } from "./report.ts";
30
- export { formatUsage, summarizeUsage, type UsageSummary } from "./usage.ts";
33
+ export { formatUsage } from "./usage.ts";
31
34
  export { findWorkspace } from "./workspace.ts";
package/src/labels.ts CHANGED
@@ -7,6 +7,7 @@ export const STATUS_LABELS: Record<WorkStatus, string> = {
7
7
  waiting_input: "利用者の入力待ち",
8
8
  waiting_approval: "承認待ち",
9
9
  waiting_external: "外部の応答待ち",
10
+ paused: "停止中",
10
11
  completed: "完了",
11
12
  failed: "失敗",
12
13
  cancelled: "取り消し",
package/src/report.ts CHANGED
@@ -2,12 +2,13 @@ import {
2
2
  type AnyEvent,
3
3
  type Event,
4
4
  pendingQuestions,
5
+ summarizeUsage,
5
6
  type ToolContent,
6
7
  type Work,
7
8
  } from "@openshain/core";
8
9
  import { describeInput, truncate } from "./format.ts";
9
10
  import { failureLabel, rejectionLabel, statusLabel } from "./labels.ts";
10
- import { formatUsage, summarizeUsage } from "./usage.ts";
11
+ import { formatUsage } from "./usage.ts";
11
12
 
12
13
  /** One line for a tool call, a rejection or a failure; nothing for the other events. `names` maps call ids to tool names. */
13
14
  export function progressLine(event: AnyEvent, names: Map<string, string>): string | undefined {
package/src/usage.ts CHANGED
@@ -1,37 +1,4 @@
1
- import { type AnyEvent, countToolCalls, type Event } from "@openshain/core";
2
-
3
- export interface UsageSummary {
4
- modelCalls: number;
5
- toolCalls: number;
6
- inputTokens: number;
7
- /** The part of inputTokens a prompt cache served. */
8
- cachedInputTokens: number;
9
- outputTokens: number;
10
- }
11
-
12
- /** Totals over a work's events: calls and tokens. */
13
- export function summarizeUsage(events: AnyEvent[]): UsageSummary {
14
- const summary: UsageSummary = {
15
- modelCalls: 0,
16
- toolCalls: 0,
17
- inputTokens: 0,
18
- cachedInputTokens: 0,
19
- outputTokens: 0,
20
- };
21
- for (const event of events) {
22
- if (event.type === "model.requested") summary.modelCalls += 1;
23
- if (event.type === "usage.recorded") {
24
- const { payload } = event as Event<"usage.recorded">;
25
- if (payload.kind === "model_inference") {
26
- summary.inputTokens += payload.usage.inputTokens;
27
- summary.cachedInputTokens += payload.usage.cachedInputTokens ?? 0;
28
- summary.outputTokens += payload.usage.outputTokens;
29
- }
30
- }
31
- }
32
- summary.toolCalls = countToolCalls(events);
33
- return summary;
34
- }
1
+ import type { UsageSummary } from "@openshain/core";
35
2
 
36
3
  export function formatUsage(summary: UsageSummary): string {
37
4
  if (summary.modelCalls === 0 && summary.inputTokens === 0 && summary.outputTokens === 0) {