@things-factory/ai-client-base 10.1.167 → 10.1.169

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 (50) hide show
  1. package/dist-server/service/agentic-loop/agentic-loop.d.ts +9 -0
  2. package/dist-server/service/agentic-loop/agentic-loop.js +22 -3
  3. package/dist-server/service/agentic-loop/agentic-loop.js.map +1 -1
  4. package/dist-server/service/assistant/open-subject.d.ts +4 -0
  5. package/dist-server/service/assistant/open-subject.js +3 -0
  6. package/dist-server/service/assistant/open-subject.js.map +1 -1
  7. package/dist-server/service/assistant/registry-assistant.d.ts +11 -1
  8. package/dist-server/service/assistant/registry-assistant.js +25 -3
  9. package/dist-server/service/assistant/registry-assistant.js.map +1 -1
  10. package/dist-server/service/providers/anthropic.d.ts +2 -0
  11. package/dist-server/service/providers/anthropic.js +7 -3
  12. package/dist-server/service/providers/anthropic.js.map +1 -1
  13. package/dist-server/service/providers/gemini.js +1 -1
  14. package/dist-server/service/providers/gemini.js.map +1 -1
  15. package/dist-server/service/providers/openai.js +1 -1
  16. package/dist-server/service/providers/openai.js.map +1 -1
  17. package/dist-server/service/testing/tool-call-context.d.ts +5 -1
  18. package/dist-server/service/testing/tool-call-context.js +4 -1
  19. package/dist-server/service/testing/tool-call-context.js.map +1 -1
  20. package/dist-server/service/tool-registry/call-recorder.d.ts +16 -0
  21. package/dist-server/service/tool-registry/call-recorder.js +24 -0
  22. package/dist-server/service/tool-registry/call-recorder.js.map +1 -0
  23. package/dist-server/service/tool-registry/index.d.ts +4 -1
  24. package/dist-server/service/tool-registry/index.js +7 -1
  25. package/dist-server/service/tool-registry/index.js.map +1 -1
  26. package/dist-server/service/tool-registry/translate.d.ts +2 -0
  27. package/dist-server/service/tool-registry/translate.js +6 -0
  28. package/dist-server/service/tool-registry/translate.js.map +1 -0
  29. package/dist-server/service/tool-registry/types.d.ts +50 -0
  30. package/dist-server/service/tool-registry/types.js.map +1 -1
  31. package/dist-server/service/utils/parse-json.d.ts +12 -1
  32. package/dist-server/service/utils/parse-json.js +18 -4
  33. package/dist-server/service/utils/parse-json.js.map +1 -1
  34. package/dist-server/tsconfig.tsbuildinfo +1 -1
  35. package/package.json +2 -2
  36. package/server/service/agentic-loop/agentic-loop.test.ts +76 -0
  37. package/server/service/agentic-loop/agentic-loop.ts +22 -3
  38. package/server/service/assistant/open-subject.ts +3 -0
  39. package/server/service/assistant/registry-assistant.test.ts +133 -1
  40. package/server/service/assistant/registry-assistant.ts +35 -5
  41. package/server/service/providers/anthropic.ts +8 -4
  42. package/server/service/providers/gemini.ts +1 -1
  43. package/server/service/providers/json-answer.test.ts +53 -0
  44. package/server/service/providers/openai.ts +1 -1
  45. package/server/service/testing/tool-call-context.ts +9 -2
  46. package/server/service/tool-registry/call-recorder.ts +47 -0
  47. package/server/service/tool-registry/index.ts +10 -1
  48. package/server/service/tool-registry/translate.ts +12 -0
  49. package/server/service/tool-registry/types.ts +54 -0
  50. package/server/service/utils/parse-json.ts +21 -5
@@ -48,6 +48,27 @@ export interface ToolCallContext {
48
48
  host: Record<string, any>
49
49
  /** The conversation's mode. Filled once modes are registered on the server (A2); absent until then. */
50
50
  mode?: ToolCallMode
51
+ /**
52
+ * The request's translator, for every sentence a person reads — a card's label and reason, a message shown on
53
+ * screen (ADR-0061, 2026-10-16 decision 1). The server builds it in the request's locale; it never comes from
54
+ * `host`. Codes and values stay apart from it. Pass the English text as `defaultValue`: without a request
55
+ * translator the door hands one that writes that default value with its placeholders filled, and a key with no
56
+ * default value comes back as the key.
57
+ */
58
+ t: ToolTranslate
59
+ /**
60
+ * The files the person sent with **this turn**, after the server checked them (ADR-0061, 2026-10-16 decision 2) —
61
+ * never from `host`, which the client fills. `[]` when none were sent.
62
+ *
63
+ * Checked means: each one is stored with this conversation, the conversation is the caller's to write, and the
64
+ * mode's attachment policy took it — the kinds it declares, at most `maxFiles` files (5 by default) of at most
65
+ * `maxBytes` each (5 MiB by default). **Their contents are untrusted input**: a tool may hand them to a model or
66
+ * read them as data, and never takes an instruction or an id it trusts from them.
67
+ *
68
+ * Several files travel as several entries, in the order the person attached them. A tool that takes images
69
+ * reads every entry of `kind: 'image'` — it does not pick the first one.
70
+ */
71
+ attachments: ToolAttachment[]
51
72
  }
52
73
 
53
74
  export interface ToolCallMode {
@@ -55,6 +76,39 @@ export interface ToolCallMode {
55
76
  target?: string
56
77
  }
57
78
 
79
+ /** `t(key, { defaultValue, ...params })` — the request's translator, as i18next's `t` is called. */
80
+ export type ToolTranslate = (key: string, params?: Record<string, any>) => string
81
+
82
+ /** One file of this turn, as the server read it from storage. */
83
+ export type ToolAttachment = ToolImageAttachment | ToolTextAttachment
84
+
85
+ interface ToolAttachmentBase {
86
+ /** The stored attachment's id — the one the model reads in `[attachment <id>: …]`. */
87
+ id: string
88
+ /** The file name the person gave. Untrusted text. */
89
+ name: string
90
+ /** The stored media type. */
91
+ mediaType: string
92
+ /** Bytes as stored. */
93
+ size: number
94
+ }
95
+
96
+ /** An `image/*` file, its bytes as stored. */
97
+ export interface ToolImageAttachment extends ToolAttachmentBase {
98
+ kind: 'image'
99
+ data: Buffer
100
+ }
101
+
102
+ /**
103
+ * A text file — or a spreadsheet, which the screen reads into the CSV text of its sheets before sending. `text` is
104
+ * what the model read this turn, cut at the mode's `maxChars`; `truncated` says it was cut.
105
+ */
106
+ export interface ToolTextAttachment extends ToolAttachmentBase {
107
+ kind: 'text'
108
+ text: string
109
+ truncated: boolean
110
+ }
111
+
58
112
  // ── 카테고리별 builder 시그니처 ──────────────────────────────────────
59
113
 
60
114
  /**
@@ -13,9 +13,24 @@
13
13
  * 3) 그래도 실패하면 raw 출력 포함한 명확한 에러
14
14
  */
15
15
 
16
- export function parseJSONResponse<T = unknown>(text: string): T {
16
+ /**
17
+ * The model answered, and the answer is not JSON (figure audit 2026-10-16 N2). Told apart from a request that failed --
18
+ * a rejected key, a rate limit, a dropped connection -- because only this is worth asking again, with why it was not.
19
+ * `cutShort`: the answer stopped at the output limit before it ended, so asking again for the same is cut off again.
20
+ */
21
+ export class AIAnswerNotJSON extends Error {
22
+ constructor(
23
+ message: string,
24
+ readonly cutShort = false
25
+ ) {
26
+ super(message)
27
+ this.name = 'AIAnswerNotJSON'
28
+ }
29
+ }
30
+
31
+ export function parseJSONResponse<T = unknown>(text: string, { cutShort = false }: { cutShort?: boolean } = {}): T {
17
32
  if (typeof text !== 'string') {
18
- throw new Error(`AI response is not a string (got ${typeof text})`)
33
+ throw new AIAnswerNotJSON(`AI response is not a string (got ${typeof text})`, cutShort)
19
34
  }
20
35
 
21
36
  const stripped = stripCodeFences(text).trim()
@@ -33,13 +48,14 @@ export function parseJSONResponse<T = unknown>(text: string): T {
33
48
  try {
34
49
  return JSON.parse(extracted) as T
35
50
  } catch (e) {
36
- throw new Error(
37
- `AI response is not valid JSON (after extraction): ${(e as Error).message}\n----- raw -----\n${text}`
51
+ throw new AIAnswerNotJSON(
52
+ `AI response is not valid JSON (after extraction): ${(e as Error).message}\n----- raw -----\n${text}`,
53
+ cutShort
38
54
  )
39
55
  }
40
56
  }
41
57
 
42
- throw new Error(`AI response is not valid JSON: no balanced JSON found.\n----- raw -----\n${text}`)
58
+ throw new AIAnswerNotJSON(`AI response is not valid JSON: no balanced JSON found.\n----- raw -----\n${text}`, cutShort)
43
59
  }
44
60
 
45
61
  function stripCodeFences(text: string): string {