@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
@@ -1 +1 @@
1
- {"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../../server/service/providers/anthropic.ts"],"names":[],"mappings":";;;;AAAA;;;;;GAKG;AACH,oEAAyC;AAczC,8CAAkD;AAClD,0DAA0D;AAE1D,MAAM,aAAa,GAAG,mBAAmB,CAAA;AACzC,MAAM,kBAAkB,GAAG,IAAI,CAAA;AAE/B,MAAa,iBAAiB;IAK5B,YAAY,MAAsB;QAChC,IAAI,CAAC,GAAG,GAAG,IAAI,aAAS,CAAC;YACvB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;SACnD,CAAC,CAAA;QACF,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,KAAK,IAAI,aAAa,CAAA;QACjD,IAAI,CAAC,EAAE,GAAG,aAAa,IAAI,CAAC,YAAY,EAAE,CAAA;IAC5C,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,QAAqB,EAAE,UAAyB,EAAE;QAC3D,MAAM,kBAAkB,GAAG,QAAQ;aAChC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC;aAChC,GAAG,CAAC,kBAAkB,CAAC;aACvB,MAAM,CAAC,OAAO,CAAC;aACf,IAAI,CAAC,MAAM,CAAC,CAAA;QACf,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,kBAAkB,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAEtF,MAAM,YAAY,GAAG,QAAQ;aAC1B,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC;aAChC,GAAG,CAAC,kBAAkB,CAAC,CAAA;QAE1B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC9C,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,YAAY;YACzC,UAAU,EAAE,OAAO,CAAC,SAAS,IAAI,kBAAkB;YACnD,GAAG,CAAC,OAAO,CAAC,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;YAC9E,GAAG,CAAC,MAAM,IAAI,EAAE,MAAM,EAAE,CAAC;YACzB,GAAG,CAAC,OAAO,CAAC,aAAa,IAAI,EAAE,cAAc,EAAE,OAAO,CAAC,aAAa,EAAE,CAAC;YACvE,QAAQ,EAAE,YAAY;SAChB,CAAC,CAAA;QACT,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,YAAY,CAAC,CAAA;QAElE,OAAO,mBAAmB,CAAC,QAAQ,CAAC,CAAA;IACtC,CAAC;IAED,KAAK,CAAC,YAAY,CAChB,MAA4B,EAC5B,UAAyB,EAAE;QAE3B,MAAM,QAAQ,GACZ,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;QAE3E,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM;YAC/B,CAAC,CAAC,mDAAmD,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;YACrF,CAAC,CAAC,EAAE,CAAA;QACN,MAAM,cAAc,GAClB,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3D,iEAAiE;YACjE,UAAU,CAAA;QAEZ,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,GAAG,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,CAAC,CAAA;QACpF,OAAO,IAAA,iCAAiB,EAAI,IAAI,CAAC,CAAA;IACnC,CAAC;IAED,KAAK,CAAC,MAAM,CACV,KAA4C,EAC5C,MAAc,EACd,UAA2B,EAAE;QAE7B,MAAM,SAAS,GACb,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,IAAI,KAAK;YACzC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,EAAE;YAC5D,CAAC,CAAC;gBACE,IAAI,EAAE,OAAO;gBACb,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,KAA4B,EAAE,SAAS,EAAE,WAAW,EAAE;aACvF,CAAA;QAEP,OAAO,IAAI,CAAC,IAAI,CACd,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,EACxE,OAAO,CACR,CAAA;IACH,CAAC;IAED,KAAK,CAAC,aAAa,CACjB,QAAqB,EACrB,KAAe,EACf,UAA6B,EAAE;QAE/B,MAAM,kBAAkB,GAAG,QAAQ;aAChC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC;aAChC,GAAG,CAAC,kBAAkB,CAAC;aACvB,MAAM,CAAC,OAAO,CAAC;aACf,IAAI,CAAC,MAAM,CAAC,CAAA;QACf,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,kBAAkB,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACtF,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAA;QAEtF,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC/B,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,YAAY,EAAE,CAAC,CAAC,UAAiB;SAClC,CAAC,CAAC,CAAA;QAEH,MAAM,UAAU,GAAG,wBAAwB,CAAC,OAAO,CAAC,CAAA;QAEpD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC9C,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,YAAY;YACzC,UAAU,EAAE,OAAO,CAAC,SAAS,IAAI,kBAAkB;YACnD,GAAG,CAAC,OAAO,CAAC,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;YAC9E,GAAG,CAAC,MAAM,IAAI,EAAE,MAAM,EAAE,CAAC;YACzB,GAAG,CAAC,OAAO,CAAC,aAAa,IAAI,EAAE,cAAc,EAAE,OAAO,CAAC,aAAa,EAAE,CAAC;YACvE,QAAQ,EAAE,YAAY;YACtB,KAAK,EAAE,QAAQ;YACf,GAAG,CAAC,UAAU,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;SACxC,CAAC,CAAA;QACT,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,YAAY,CAAC,CAAA;QAElE,MAAM,IAAI,GAAG,mBAAmB,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAA;QACvD,MAAM,SAAS,GAAiB,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;YAC7D,CAAC,CAAC,QAAQ,CAAC,OAAO;iBACb,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC;iBAC9C,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC;gBAChB,EAAE,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE;gBAC/F,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,SAAS,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAwB;aAC1F,CAAC,CAAC;YACP,CAAC,CAAC,EAAE,CAAA;QAEN,OAAO;YACL,IAAI;YACJ,SAAS;YACT,UAAU,EAAE,4BAA4B,CAAC,QAAQ,CAAC,WAAW,CAAC;SAC/D,CAAA;IACH,CAAC;IAED,2DAA2D;IAC3D,KAAK,CAAC,UAAU;QACd,MAAM,GAAG,GAAa,EAAE,CAAA;QACxB,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE;YAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;QACpE,OAAO,GAAG,CAAC,IAAI,EAAE,CAAA;IACnB,CAAC;CACF;AAtID,8CAsIC;AAED,SAAS,WAAW,CAAC,QAAa,EAAE,OAAsB,EAAE,SAAiB;IAC3E,OAAO,CAAC,OAAO,EAAE,CAAC;QAChB,KAAK,EAAE,QAAQ,EAAE,KAAK,IAAI,SAAS;QACnC,WAAW,EAAE,QAAQ,EAAE,KAAK,EAAE,YAAY;QAC1C,YAAY,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa;KAC7C,CAAC,CAAA;AACJ,CAAC;AAED,qEAAqE;AAErE,SAAS,kBAAkB,CAAC,GAAc;IACxC,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAC,OAAO,CAAA;IACvD,OAAO,GAAG,CAAC,OAAO;SACf,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;SAC9B,GAAG,CAAC,CAAC,CAAC,EAAE,CAAE,CAAoC,CAAC,IAAI,CAAC;SACpD,IAAI,CAAC,IAAI,CAAC,CAAA;AACf,CAAC;AAED,SAAS,kBAAkB,CAAC,GAAc;IACxC,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QACpC,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAA;IACjD,CAAC;IACD,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC;KAClD,CAAA;AACH,CAAC;AAED,SAAS,uBAAuB,CAAC,IAAmB;IAClD,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACzB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAA;IAC1C,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC7B,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,CAAA;IAClF,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;QAChC,OAAO;YACL,IAAI,EAAE,aAAa;YACnB,WAAW,EAAE,IAAI,CAAC,SAAS;YAC3B,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;SACxC,CAAA;IACH,CAAC;IACD,QAAQ;IACR,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QAC/B,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,CAAA;IACzE,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;QAC5C,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACrC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IACpD,OAAO;QACL,IAAI,EAAE,OAAO;QACb,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,EAAE;KACpE,CAAA;AACH,CAAC;AAED,SAAS,mBAAmB,CAAC,QAAa;IACxC,IAAI,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,CAAA;IAC5D,OAAO,QAAQ,CAAC,OAAO;SACpB,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;SAC1C,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;SAC7B,IAAI,CAAC,EAAE,CAAC,CAAA;AACb,CAAC;AAED,SAAS,wBAAwB,CAAC,OAA0B;IAC1D,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,IAAI,MAAM,CAAA;IAC3C,MAAM,eAAe,GAAG,OAAO,CAAC,sBAAsB,KAAK,KAAK,CAAA;IAChE,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;IACzB,CAAC;IACD,IAAI,MAAM,KAAK,UAAU,EAAE,CAAC;QAC1B,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;YACtB,OAAO;gBACL,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,OAAO,CAAC,SAAS;gBACvB,GAAG,CAAC,eAAe,IAAI,EAAE,yBAAyB,EAAE,IAAI,EAAE,CAAC;aAC5D,CAAA;QACH,CAAC;QACD,OAAO;YACL,IAAI,EAAE,KAAK;YACX,GAAG,CAAC,eAAe,IAAI,EAAE,yBAAyB,EAAE,IAAI,EAAE,CAAC;SAC5D,CAAA;IACH,CAAC;IACD,SAAS;IACT,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,GAAG,CAAC,eAAe,IAAI,EAAE,yBAAyB,EAAE,IAAI,EAAE,CAAC;KAC5D,CAAA;AACH,CAAC;AAED,SAAS,4BAA4B,CAAC,GAAQ;IAC5C,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,UAAU;YACb,OAAO,UAAU,CAAA;QACnB,KAAK,UAAU;YACb,OAAO,UAAU,CAAA;QACnB,KAAK,YAAY;YACf,OAAO,YAAY,CAAA;QACrB,KAAK,eAAe;YAClB,OAAO,eAAe,CAAA;QACxB;YACE,OAAO,GAAG,IAAI,UAAU,CAAA;IAC5B,CAAC;AACH,CAAC;AAED,+BAA+B;AAC/B,IAAA,+BAAkB,EAAC,WAAW,EAAE,MAAM,CAAC,EAAE,CAAC,IAAI,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAA","sourcesContent":["/**\n * Anthropic (Claude) AI Client adapter.\n *\n * 기본 모델: claude-sonnet-4-6 (구성에서 override 가능).\n * SDK: @anthropic-ai/sdk\n */\nimport Anthropic from '@anthropic-ai/sdk'\nimport type {\n AIChatOptions,\n AIClient,\n AIClientConfig,\n AIContentPart,\n AIJSONOptions,\n AIMessage,\n AITool,\n AIToolCall,\n AIToolChatOptions,\n AIToolChatResult,\n AIVisionOptions\n} from '../types.js'\nimport { registerAIProvider } from '../factory.js'\nimport { parseJSONResponse } from '../utils/parse-json.js'\n\nconst DEFAULT_MODEL = 'claude-sonnet-4-6'\nconst DEFAULT_MAX_TOKENS = 4096\n\nexport class AnthropicAIClient implements AIClient {\n readonly id: string\n private sdk: Anthropic\n private defaultModel: string\n\n constructor(config: AIClientConfig) {\n this.sdk = new Anthropic({\n apiKey: config.apiKey,\n ...(config.baseURL && { baseURL: config.baseURL })\n })\n this.defaultModel = config.model ?? DEFAULT_MODEL\n this.id = `anthropic:${this.defaultModel}`\n }\n\n async chat(messages: AIMessage[], options: AIChatOptions = {}): Promise<string> {\n const systemFromMessages = messages\n .filter(m => m.role === 'system')\n .map(extractTextContent)\n .filter(Boolean)\n .join('\\n\\n')\n const system = [options.systemPrompt, systemFromMessages].filter(Boolean).join('\\n\\n')\n\n const conversation = messages\n .filter(m => m.role !== 'system')\n .map(toAnthropicMessage)\n\n const response = await this.sdk.messages.create({\n model: options.model ?? this.defaultModel,\n max_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n ...(system && { system }),\n ...(options.stopSequences && { stop_sequences: options.stopSequences }),\n messages: conversation\n } as any)\n reportUsage(response, options, options.model ?? this.defaultModel)\n\n return extractResponseText(response)\n }\n\n async generateJSON<T = unknown>(\n prompt: string | AIMessage[],\n options: AIJSONOptions = {}\n ): Promise<T> {\n const messages: AIMessage[] =\n typeof prompt === 'string' ? [{ role: 'user', content: prompt }] : prompt\n\n const schemaHint = options.schema\n ? `\\n\\nResponse MUST conform to this JSON schema:\\n${JSON.stringify(options.schema)}`\n : ''\n const enforcedSystem =\n (options.systemPrompt ? options.systemPrompt + '\\n\\n' : '') +\n 'Return only valid JSON. No markdown code fences, no commentary.' +\n schemaHint\n\n const text = await this.chat(messages, { ...options, systemPrompt: enforcedSystem })\n return parseJSONResponse<T>(text)\n }\n\n async vision(\n image: Buffer | Uint8Array | { url: string },\n prompt: string,\n options: AIVisionOptions = {}\n ): Promise<string> {\n const imagePart: AIContentPart =\n typeof image === 'object' && 'url' in image\n ? { type: 'image', source: { kind: 'url', url: image.url } }\n : {\n type: 'image',\n source: { kind: 'base64', data: image as Buffer | Uint8Array, mediaType: 'image/png' }\n }\n\n return this.chat(\n [{ role: 'user', content: [imagePart, { type: 'text', text: prompt }] }],\n options\n )\n }\n\n async chatWithTools(\n messages: AIMessage[],\n tools: AITool[],\n options: AIToolChatOptions = {}\n ): Promise<AIToolChatResult> {\n const systemFromMessages = messages\n .filter(m => m.role === 'system')\n .map(extractTextContent)\n .filter(Boolean)\n .join('\\n\\n')\n const system = [options.systemPrompt, systemFromMessages].filter(Boolean).join('\\n\\n')\n const conversation = messages.filter(m => m.role !== 'system').map(toAnthropicMessage)\n\n const sdkTools = tools.map(t => ({\n name: t.name,\n description: t.description,\n input_schema: t.parameters as any\n }))\n\n const toolChoice = buildAnthropicToolChoice(options)\n\n const response = await this.sdk.messages.create({\n model: options.model ?? this.defaultModel,\n max_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n ...(system && { system }),\n ...(options.stopSequences && { stop_sequences: options.stopSequences }),\n messages: conversation,\n tools: sdkTools,\n ...(toolChoice && { tool_choice: toolChoice })\n } as any)\n reportUsage(response, options, options.model ?? this.defaultModel)\n\n const text = extractResponseText(response) || undefined\n const toolCalls: AIToolCall[] = Array.isArray(response.content)\n ? response.content\n .filter((b: any) => b && b.type === 'tool_use')\n .map((b: any) => ({\n id: typeof b.id === 'string' && b.id ? b.id : `call_${Math.random().toString(36).slice(2, 12)}`,\n name: b.name,\n arguments: (b.input && typeof b.input === 'object' ? b.input : {}) as Record<string, any>\n }))\n : []\n\n return {\n text,\n toolCalls,\n stopReason: normalizeAnthropicStopReason(response.stop_reason)\n }\n }\n\n /** Every model Anthropic lists is a conversation model. */\n async listModels(): Promise<string[]> {\n const ids: string[] = []\n for await (const model of this.sdk.models.list()) ids.push(model.id)\n return ids.sort()\n }\n}\n\nfunction reportUsage(response: any, options: AIChatOptions, requested: string): void {\n options.onUsage?.({\n model: response?.model ?? requested,\n inputTokens: response?.usage?.input_tokens,\n outputTokens: response?.usage?.output_tokens\n })\n}\n\n// ── 변환 유틸 ─────────────────────────────────────────────────────────\n\nfunction extractTextContent(msg: AIMessage): string {\n if (typeof msg.content === 'string') return msg.content\n return msg.content\n .filter(p => p.type === 'text')\n .map(p => (p as { type: 'text'; text: string }).text)\n .join('\\n')\n}\n\nfunction toAnthropicMessage(msg: AIMessage): any {\n if (typeof msg.content === 'string') {\n return { role: msg.role, content: msg.content }\n }\n return {\n role: msg.role,\n content: msg.content.map(toAnthropicContentBlock)\n }\n}\n\nfunction toAnthropicContentBlock(part: AIContentPart): any {\n if (part.type === 'text') {\n return { type: 'text', text: part.text }\n }\n if (part.type === 'tool_use') {\n return { type: 'tool_use', id: part.id, name: part.name, input: part.arguments }\n }\n if (part.type === 'tool_result') {\n return {\n type: 'tool_result',\n tool_use_id: part.toolUseId,\n content: part.content,\n ...(part.isError && { is_error: true })\n }\n }\n // image\n if (part.source.kind === 'url') {\n return { type: 'image', source: { type: 'url', url: part.source.url } }\n }\n const data = Buffer.isBuffer(part.source.data)\n ? part.source.data.toString('base64')\n : Buffer.from(part.source.data).toString('base64')\n return {\n type: 'image',\n source: { type: 'base64', media_type: part.source.mediaType, data }\n }\n}\n\nfunction extractResponseText(response: any): string {\n if (!response || !Array.isArray(response.content)) return ''\n return response.content\n .filter((b: any) => b && b.type === 'text')\n .map((b: any) => b.text || '')\n .join('')\n}\n\nfunction buildAnthropicToolChoice(options: AIToolChatOptions): any | undefined {\n const choice = options.toolChoice ?? 'auto'\n const disableParallel = options.allowParallelToolCalls === false\n if (choice === 'none') {\n return { type: 'none' }\n }\n if (choice === 'required') {\n if (options.forceTool) {\n return {\n type: 'tool',\n name: options.forceTool,\n ...(disableParallel && { disable_parallel_tool_use: true })\n }\n }\n return {\n type: 'any',\n ...(disableParallel && { disable_parallel_tool_use: true })\n }\n }\n // 'auto'\n return {\n type: 'auto',\n ...(disableParallel && { disable_parallel_tool_use: true })\n }\n}\n\nfunction normalizeAnthropicStopReason(raw: any): AIToolChatResult['stopReason'] {\n switch (raw) {\n case 'tool_use':\n return 'tool_use'\n case 'end_turn':\n return 'end_turn'\n case 'max_tokens':\n return 'max_tokens'\n case 'stop_sequence':\n return 'stop_sequence'\n default:\n return raw ?? 'end_turn'\n }\n}\n\n// Auto-register at module load\nregisterAIProvider('anthropic', config => new AnthropicAIClient(config))\n"]}
1
+ {"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../../server/service/providers/anthropic.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Anthropic (Claude) AI Client adapter.\n *\n * 기본 모델: claude-sonnet-4-6 (구성에서 override 가능).\n * SDK: @anthropic-ai/sdk\n */\nimport Anthropic from '@anthropic-ai/sdk'\nimport type {\n AIChatOptions,\n AIClient,\n AIClientConfig,\n AIContentPart,\n AIJSONOptions,\n AIMessage,\n AITool,\n AIToolCall,\n AIToolChatOptions,\n AIToolChatResult,\n AIVisionOptions\n} from '../types.js'\nimport { registerAIProvider } from '../factory.js'\nimport { parseJSONResponse } from '../utils/parse-json.js'\n\nconst DEFAULT_MODEL = 'claude-sonnet-4-6'\nconst DEFAULT_MAX_TOKENS = 4096\n\nexport class AnthropicAIClient implements AIClient {\n readonly id: string\n private sdk: Anthropic\n private defaultModel: string\n\n constructor(config: AIClientConfig) {\n this.sdk = new Anthropic({\n apiKey: config.apiKey,\n ...(config.baseURL && { baseURL: config.baseURL })\n })\n this.defaultModel = config.model ?? DEFAULT_MODEL\n this.id = `anthropic:${this.defaultModel}`\n }\n\n async chat(messages: AIMessage[], options: AIChatOptions = {}): Promise<string> {\n return extractResponseText(await this.send(messages, options))\n }\n\n /** One request, the provider's response as it came: `generateJSON` reads whether it stopped at the output limit. */\n private async send(messages: AIMessage[], options: AIChatOptions): Promise<any> {\n const systemFromMessages = messages\n .filter(m => m.role === 'system')\n .map(extractTextContent)\n .filter(Boolean)\n .join('\\n\\n')\n const system = [options.systemPrompt, systemFromMessages].filter(Boolean).join('\\n\\n')\n\n const conversation = messages\n .filter(m => m.role !== 'system')\n .map(toAnthropicMessage)\n\n const response = await this.sdk.messages.create({\n model: options.model ?? this.defaultModel,\n max_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n ...(system && { system }),\n ...(options.stopSequences && { stop_sequences: options.stopSequences }),\n messages: conversation\n } as any)\n reportUsage(response, options, options.model ?? this.defaultModel)\n return response\n }\n\n async generateJSON<T = unknown>(\n prompt: string | AIMessage[],\n options: AIJSONOptions = {}\n ): Promise<T> {\n const messages: AIMessage[] =\n typeof prompt === 'string' ? [{ role: 'user', content: prompt }] : prompt\n\n const schemaHint = options.schema\n ? `\\n\\nResponse MUST conform to this JSON schema:\\n${JSON.stringify(options.schema)}`\n : ''\n const enforcedSystem =\n (options.systemPrompt ? options.systemPrompt + '\\n\\n' : '') +\n 'Return only valid JSON. No markdown code fences, no commentary.' +\n schemaHint\n\n const response = await this.send(messages, { ...options, systemPrompt: enforcedSystem })\n return parseJSONResponse<T>(extractResponseText(response), { cutShort: response?.stop_reason === 'max_tokens' })\n }\n\n async vision(\n image: Buffer | Uint8Array | { url: string },\n prompt: string,\n options: AIVisionOptions = {}\n ): Promise<string> {\n const imagePart: AIContentPart =\n typeof image === 'object' && 'url' in image\n ? { type: 'image', source: { kind: 'url', url: image.url } }\n : {\n type: 'image',\n source: { kind: 'base64', data: image as Buffer | Uint8Array, mediaType: 'image/png' }\n }\n\n return this.chat(\n [{ role: 'user', content: [imagePart, { type: 'text', text: prompt }] }],\n options\n )\n }\n\n async chatWithTools(\n messages: AIMessage[],\n tools: AITool[],\n options: AIToolChatOptions = {}\n ): Promise<AIToolChatResult> {\n const systemFromMessages = messages\n .filter(m => m.role === 'system')\n .map(extractTextContent)\n .filter(Boolean)\n .join('\\n\\n')\n const system = [options.systemPrompt, systemFromMessages].filter(Boolean).join('\\n\\n')\n const conversation = messages.filter(m => m.role !== 'system').map(toAnthropicMessage)\n\n const sdkTools = tools.map(t => ({\n name: t.name,\n description: t.description,\n input_schema: t.parameters as any\n }))\n\n const toolChoice = buildAnthropicToolChoice(options)\n\n const response = await this.sdk.messages.create({\n model: options.model ?? this.defaultModel,\n max_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n ...(system && { system }),\n ...(options.stopSequences && { stop_sequences: options.stopSequences }),\n messages: conversation,\n tools: sdkTools,\n ...(toolChoice && { tool_choice: toolChoice })\n } as any)\n reportUsage(response, options, options.model ?? this.defaultModel)\n\n const text = extractResponseText(response) || undefined\n const toolCalls: AIToolCall[] = Array.isArray(response.content)\n ? response.content\n .filter((b: any) => b && b.type === 'tool_use')\n .map((b: any) => ({\n id: typeof b.id === 'string' && b.id ? b.id : `call_${Math.random().toString(36).slice(2, 12)}`,\n name: b.name,\n arguments: (b.input && typeof b.input === 'object' ? b.input : {}) as Record<string, any>\n }))\n : []\n\n return {\n text,\n toolCalls,\n stopReason: normalizeAnthropicStopReason(response.stop_reason)\n }\n }\n\n /** Every model Anthropic lists is a conversation model. */\n async listModels(): Promise<string[]> {\n const ids: string[] = []\n for await (const model of this.sdk.models.list()) ids.push(model.id)\n return ids.sort()\n }\n}\n\nfunction reportUsage(response: any, options: AIChatOptions, requested: string): void {\n options.onUsage?.({\n model: response?.model ?? requested,\n inputTokens: response?.usage?.input_tokens,\n outputTokens: response?.usage?.output_tokens\n })\n}\n\n// ── 변환 유틸 ─────────────────────────────────────────────────────────\n\nfunction extractTextContent(msg: AIMessage): string {\n if (typeof msg.content === 'string') return msg.content\n return msg.content\n .filter(p => p.type === 'text')\n .map(p => (p as { type: 'text'; text: string }).text)\n .join('\\n')\n}\n\nfunction toAnthropicMessage(msg: AIMessage): any {\n if (typeof msg.content === 'string') {\n return { role: msg.role, content: msg.content }\n }\n return {\n role: msg.role,\n content: msg.content.map(toAnthropicContentBlock)\n }\n}\n\nfunction toAnthropicContentBlock(part: AIContentPart): any {\n if (part.type === 'text') {\n return { type: 'text', text: part.text }\n }\n if (part.type === 'tool_use') {\n return { type: 'tool_use', id: part.id, name: part.name, input: part.arguments }\n }\n if (part.type === 'tool_result') {\n return {\n type: 'tool_result',\n tool_use_id: part.toolUseId,\n content: part.content,\n ...(part.isError && { is_error: true })\n }\n }\n // image\n if (part.source.kind === 'url') {\n return { type: 'image', source: { type: 'url', url: part.source.url } }\n }\n const data = Buffer.isBuffer(part.source.data)\n ? part.source.data.toString('base64')\n : Buffer.from(part.source.data).toString('base64')\n return {\n type: 'image',\n source: { type: 'base64', media_type: part.source.mediaType, data }\n }\n}\n\nfunction extractResponseText(response: any): string {\n if (!response || !Array.isArray(response.content)) return ''\n return response.content\n .filter((b: any) => b && b.type === 'text')\n .map((b: any) => b.text || '')\n .join('')\n}\n\nfunction buildAnthropicToolChoice(options: AIToolChatOptions): any | undefined {\n const choice = options.toolChoice ?? 'auto'\n const disableParallel = options.allowParallelToolCalls === false\n if (choice === 'none') {\n return { type: 'none' }\n }\n if (choice === 'required') {\n if (options.forceTool) {\n return {\n type: 'tool',\n name: options.forceTool,\n ...(disableParallel && { disable_parallel_tool_use: true })\n }\n }\n return {\n type: 'any',\n ...(disableParallel && { disable_parallel_tool_use: true })\n }\n }\n // 'auto'\n return {\n type: 'auto',\n ...(disableParallel && { disable_parallel_tool_use: true })\n }\n}\n\nfunction normalizeAnthropicStopReason(raw: any): AIToolChatResult['stopReason'] {\n switch (raw) {\n case 'tool_use':\n return 'tool_use'\n case 'end_turn':\n return 'end_turn'\n case 'max_tokens':\n return 'max_tokens'\n case 'stop_sequence':\n return 'stop_sequence'\n default:\n return raw ?? 'end_turn'\n }\n}\n\n// Auto-register at module load\nregisterAIProvider('anthropic', config => new AnthropicAIClient(config))\n"]}
@@ -48,7 +48,7 @@ class GeminiAIClient {
48
48
  const result = await model.generateContent({ contents });
49
49
  reportUsage(result, options, options.model ?? this.defaultModel);
50
50
  const text = result.response.text();
51
- return (0, parse_json_js_1.parseJSONResponse)(text);
51
+ return (0, parse_json_js_1.parseJSONResponse)(text, { cutShort: result.response.candidates?.[0]?.finishReason === 'MAX_TOKENS' });
52
52
  }
53
53
  async vision(image, prompt, options = {}) {
54
54
  const imagePart = typeof image === 'object' && 'url' in image
@@ -1 +1 @@
1
- {"version":3,"file":"gemini.js","sourceRoot":"","sources":["../../../server/service/providers/gemini.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Google Gemini AI Client adapter.\n *\n * 기본 모델: gemini-3-pro (구성에서 override 가능).\n * SDK: @google/generative-ai\n *\n * 비고: Antigravity (Google 의 agentic IDE) 가 기본 백엔드로 사용하는 모델군.\n * 이 어댑터는 Gemini API 직접 호출 — Antigravity IDE 와는 별개.\n */\nimport { GoogleGenerativeAI } from '@google/generative-ai'\nimport type {\n AIChatOptions,\n AIClient,\n AIClientConfig,\n AIContentPart,\n AIJSONOptions,\n AIMessage,\n AITool,\n AIToolCall,\n AIToolChatOptions,\n AIToolChatResult,\n AIVisionOptions\n} from '../types.js'\nimport { registerAIProvider } from '../factory.js'\nimport { parseJSONResponse } from '../utils/parse-json.js'\n\n// `gemini-3-pro` 는 2026-03-09 종료됨. 후속 권장 모델로 변경.\n// 사용자가 자기 환경에서 활용 가능한 정확한 ID 는 ListModels 로 확인:\n// curl \"https://generativelanguage.googleapis.com/v1beta/models?key=$KEY\"\nconst DEFAULT_MODEL = 'gemini-3-flash-preview'\nconst DEFAULT_MAX_TOKENS = 8192\n\nexport class GeminiAIClient implements AIClient {\n readonly id: string\n private sdk: GoogleGenerativeAI\n private defaultModel: string\n private apiKey: string\n private baseURL?: string\n\n constructor(config: AIClientConfig) {\n this.apiKey = config.apiKey\n this.baseURL = config.baseURL\n this.sdk = new GoogleGenerativeAI(config.apiKey)\n this.defaultModel = config.model ?? DEFAULT_MODEL\n this.id = `gemini:${this.defaultModel}`\n }\n\n async chat(messages: AIMessage[], options: AIChatOptions = {}): Promise<string> {\n const { systemPrompt, contents } = splitMessages(messages, options.systemPrompt)\n const model = this.buildModel(options, systemPrompt)\n const result = await model.generateContent({ contents } as any)\n reportUsage(result, options, options.model ?? this.defaultModel)\n return result.response.text()\n }\n\n async generateJSON<T = unknown>(\n prompt: string | AIMessage[],\n options: AIJSONOptions = {}\n ): Promise<T> {\n const messages: AIMessage[] =\n typeof prompt === 'string' ? [{ role: 'user', content: prompt }] : prompt\n const { systemPrompt, contents } = splitMessages(messages, options.systemPrompt)\n\n const generationConfig: any = baseGenerationConfig(options)\n generationConfig.responseMimeType = 'application/json'\n if (options.schema) generationConfig.responseSchema = options.schema\n\n const model = this.sdk.getGenerativeModel({\n model: options.model ?? this.defaultModel,\n ...(systemPrompt && { systemInstruction: systemPrompt }),\n generationConfig\n }, this.requestOptions)\n const result = await model.generateContent({ contents } as any)\n reportUsage(result, options, options.model ?? this.defaultModel)\n const text = result.response.text()\n return parseJSONResponse<T>(text)\n }\n\n async vision(\n image: Buffer | Uint8Array | { url: string },\n prompt: string,\n options: AIVisionOptions = {}\n ): Promise<string> {\n const imagePart: AIContentPart =\n typeof image === 'object' && 'url' in image\n ? { type: 'image', source: { kind: 'url', url: image.url } }\n : {\n type: 'image',\n source: { kind: 'base64', data: image as Buffer | Uint8Array, mediaType: 'image/png' }\n }\n\n return this.chat(\n [{ role: 'user', content: [imagePart, { type: 'text', text: prompt }] }],\n options\n )\n }\n\n private buildModel(options: AIChatOptions, systemInstruction?: string) {\n return this.sdk.getGenerativeModel({\n model: options.model ?? this.defaultModel,\n ...(systemInstruction && { systemInstruction }),\n generationConfig: baseGenerationConfig(options)\n }, this.requestOptions)\n }\n\n async chatWithTools(\n messages: AIMessage[],\n tools: AITool[],\n options: AIToolChatOptions = {}\n ): Promise<AIToolChatResult> {\n const { systemPrompt, contents } = splitMessages(messages, options.systemPrompt)\n\n // Gemini 는 OpenAPI 3.0 subset — JSON Schema 의 일부 키를 거부함.\n // 'additionalProperties' / '$schema' / '$defs' 등을 제거.\n const functionDeclarations = tools.map(t => ({\n name: t.name,\n description: t.description,\n parameters: sanitizeForGeminiSchema(t.parameters)\n }))\n\n const toolConfig = buildGeminiToolConfig(options)\n\n const model = this.sdk.getGenerativeModel({\n model: options.model ?? this.defaultModel,\n ...(systemPrompt && { systemInstruction: systemPrompt }),\n generationConfig: baseGenerationConfig(options),\n tools: [{ functionDeclarations }] as any,\n ...(toolConfig && { toolConfig })\n } as any, this.requestOptions)\n\n const result = await model.generateContent({ contents } as any)\n reportUsage(result, options, options.model ?? this.defaultModel)\n const response: any = result.response\n\n const candidate = response?.candidates?.[0]\n const parts = candidate?.content?.parts ?? []\n\n // Gemini 는 한 응답에서 (a) thinking part (p.thought === true) 와\n // (b) 본문 text part 를 함께 emit 할 수 있다. thought 는 사용자에게 노출하면\n // \"구조화 답변 + 산문 답변\" 형태의 중복으로 보이므로 제거.\n const textParts = parts\n .filter((p: any) => typeof p?.text === 'string' && p?.thought !== true)\n .map((p: any) => p.text)\n const text = textParts.join('') || undefined\n\n // Gemini 는 functionCall 에 자체 id 가 없음 — 어댑터가 turn 내부에서 부여.\n // id 포맷 'g:<name>:<seq>' — assistant 가 tool_result 를 만들 때 이 id 를 그대로\n // 넘기면 toGeminiPart 가 name 을 추출해 functionResponse.name 에 채운다.\n //\n // thoughtSignature: Gemini 2.5/3 부터 thinking 모델이 functionCall 에 서명을 붙임.\n // 다음 turn 에 그대로 echo 해야 400 에러 안 남 (cite: ai.google.dev/.../thought-signatures).\n let geminiCallSeq = 0\n const ts = Date.now()\n const toolCalls: AIToolCall[] = parts\n .filter((p: any) => p?.functionCall && typeof p.functionCall.name === 'string')\n .map((p: any) => {\n // SDK 가 camelCase 또는 snake_case 둘 중 하나로 emit — 둘 다 받음.\n const sig: string | undefined = p.thoughtSignature ?? p.thought_signature\n return {\n id: `g:${p.functionCall.name}:${ts}_${geminiCallSeq++}`,\n name: p.functionCall.name,\n arguments:\n p.functionCall.args && typeof p.functionCall.args === 'object'\n ? (p.functionCall.args as Record<string, any>)\n : {},\n ...(typeof sig === 'string' && sig.length > 0 && {\n providerMeta: { thoughtSignature: sig }\n })\n }\n })\n\n return {\n text,\n toolCalls,\n stopReason: normalizeGeminiStopReason(candidate?.finishReason, toolCalls.length > 0)\n }\n }\n\n /**\n * The SDK has no model list, so this reads the REST one. A conversation model is one that answers\n * generateContent; embedding and other models are left out.\n */\n async listModels(): Promise<string[]> {\n const base = (this.baseURL || 'https://generativelanguage.googleapis.com').replace(/\\/+$/, '')\n const ids: string[] = []\n let pageToken = ''\n do {\n const url = `${base}/v1beta/models?pageSize=1000${pageToken ? `&pageToken=${encodeURIComponent(pageToken)}` : ''}`\n const response = await fetch(url, { headers: { 'x-goog-api-key': this.apiKey } })\n if (!response.ok) throw new ProviderListError(response.status, await response.text())\n const page: any = await response.json()\n for (const model of page.models ?? []) {\n if ((model.supportedGenerationMethods ?? []).includes('generateContent')) {\n ids.push(String(model.name).replace(/^models\\//, ''))\n }\n }\n pageToken = page.nextPageToken ?? ''\n } while (pageToken)\n return ids.sort()\n }\n\n private get requestOptions() {\n return this.baseURL ? { baseUrl: this.baseURL } : undefined\n }\n}\n\n/** A model list the provider refused, with the HTTP status the caller turns into a reason. */\nexport class ProviderListError extends Error {\n constructor(readonly status: number, body: string) {\n super(`model list failed with ${status}: ${body.slice(0, 200)}`)\n }\n}\n\nfunction reportUsage(result: any, options: AIChatOptions, requested: string): void {\n const usage = result?.response?.usageMetadata\n options.onUsage?.({\n model: result?.response?.modelVersion ?? requested,\n inputTokens: usage?.promptTokenCount,\n outputTokens: usage?.candidatesTokenCount\n })\n}\n\n// ── 변환 유틸 ─────────────────────────────────────────────────────────\n\nfunction baseGenerationConfig(options: AIChatOptions): any {\n return {\n maxOutputTokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n ...(options.stopSequences && { stopSequences: options.stopSequences })\n }\n}\n\nfunction splitMessages(\n messages: AIMessage[],\n externalSystem?: string\n): { systemPrompt: string; contents: any[] } {\n const systemMessages = messages\n .filter(m => m.role === 'system')\n .map(extractTextContent)\n .filter(Boolean)\n const conversation = messages.filter(m => m.role !== 'system')\n const systemPrompt = [externalSystem, ...systemMessages].filter(Boolean).join('\\n\\n')\n return { systemPrompt, contents: conversation.map(toGeminiContent) }\n}\n\nfunction extractTextContent(msg: AIMessage): string {\n if (typeof msg.content === 'string') return msg.content\n return msg.content\n .filter(p => p.type === 'text')\n .map(p => (p as { type: 'text'; text: string }).text)\n .join('\\n')\n}\n\nfunction toGeminiContent(msg: AIMessage): any {\n // Our 'assistant' role → Gemini's 'model' role\n const role = msg.role === 'assistant' ? 'model' : msg.role\n const parts =\n typeof msg.content === 'string'\n ? [{ text: msg.content }]\n : msg.content.map(toGeminiPart)\n return { role, parts }\n}\n\nfunction toGeminiPart(part: AIContentPart): any {\n if (part.type === 'text') {\n return { text: part.text }\n }\n if (part.type === 'tool_use') {\n // Gemini: assistant 가 호출 의도 표현. functionCall.name + args 만 가짐 (id 없음).\n // thoughtSignature 는 thinking 모델 사용 시 필수 — providerMeta 에서 복원해 echo.\n const sig = part.providerMeta?.thoughtSignature\n return {\n functionCall: { name: part.name, args: part.arguments },\n ...(typeof sig === 'string' && sig.length > 0 && { thoughtSignature: sig })\n }\n }\n if (part.type === 'tool_result') {\n // Gemini: user role 에 functionResponse part 로 회신. 이름은 호출했던 tool name 과\n // 같아야 함 — assistant.ts 의 loop 에서 part 생성 시 함께 전달.\n let parsed: any = part.content\n try {\n parsed = JSON.parse(part.content)\n } catch {\n parsed = { result: part.content }\n }\n return {\n functionResponse: {\n // name 은 원래 호출했던 tool name. toolUseId 에 임베드되어 있지 않으므로\n // assistant 가 메시지 생성 시 별도 채워줘야 함 — 여기서는 placeholder.\n // 실제 사용 시 toolUseId 를 'tool_<name>_<seq>' 형태로 만들어 name 추출.\n name: extractGeminiNameFromId(part.toolUseId),\n response: parsed\n }\n }\n }\n // image\n if (part.source.kind === 'url') {\n // Gemini SDK 는 inlineData(base64) 만 지원. URL 은 호출 전에 fetch 필요.\n throw new Error(\n 'Gemini adapter: image URL is not supported directly. Fetch the URL and pass as Buffer.'\n )\n }\n const data = Buffer.isBuffer(part.source.data)\n ? part.source.data.toString('base64')\n : Buffer.from(part.source.data).toString('base64')\n return {\n inlineData: { data, mimeType: part.source.mediaType }\n }\n}\n\n/**\n * Gemini 의 functionCall 에는 자체 id 가 없어서 chatWithTools 가 'gemini_<ts>_<seq>'\n * 형태의 가짜 id 를 만든다. 그런데 functionResponse 에는 name 이 필수 — assistant 의\n * tool 실행기는 이 id 와 name 을 별도 매핑해야 한다. assistant 가 tool_result part 를\n * 만들 때 toolUseId 에 'name:<actualName>;id:<originalId>' 같은 인코딩을 할 수 있지만\n * 그건 invasive — 대신 toolUseId 가 'g:<name>:<id>' 형태면 name 을 직접 추출.\n *\n * 현재는 fallback: id 가 'g:' 로 시작하면 두 번째 segment 를 name 으로,\n * 아니면 그대로 사용 (Gemini 가 받지 않으면 에러 — assistant 가 책임지고 인코딩).\n */\nfunction extractGeminiNameFromId(id: string): string {\n if (id.startsWith('g:')) {\n const parts = id.split(':')\n return parts[1] || id\n }\n return id\n}\n\n/**\n * JSON Schema → Gemini OpenAPI 3.0 subset 변환.\n * Gemini API 가 거부하는 키들 (deep) 제거.\n * 보존: type / properties / items / required / enum / description / format / nullable\n */\nconst GEMINI_REJECTED_KEYS = new Set([\n 'additionalProperties',\n '$schema',\n '$defs',\n '$ref',\n '$id',\n 'definitions',\n 'patternProperties',\n 'dependencies'\n])\n\nfunction sanitizeForGeminiSchema(schema: any): any {\n if (schema === null || schema === undefined) return schema\n if (Array.isArray(schema)) return schema.map(sanitizeForGeminiSchema)\n if (typeof schema !== 'object') return schema\n const out: any = {}\n for (const [k, v] of Object.entries(schema)) {\n if (GEMINI_REJECTED_KEYS.has(k)) continue\n out[k] = sanitizeForGeminiSchema(v)\n }\n return out\n}\n\nfunction buildGeminiToolConfig(options: AIToolChatOptions): any | undefined {\n const choice = options.toolChoice ?? 'auto'\n if (choice === 'none') {\n return { functionCallingConfig: { mode: 'NONE' } }\n }\n if (choice === 'required') {\n return {\n functionCallingConfig: {\n mode: 'ANY',\n ...(options.forceTool && { allowedFunctionNames: [options.forceTool] })\n }\n }\n }\n return { functionCallingConfig: { mode: 'AUTO' } }\n}\n\nfunction normalizeGeminiStopReason(\n raw: any,\n hasToolCalls: boolean\n): AIToolChatResult['stopReason'] {\n // Gemini 는 function call 발생 시 finishReason 을 'STOP' 으로 마킹할 수 있어\n // toolCalls 존재 여부로 우선 판정.\n if (hasToolCalls) return 'tool_use'\n switch (raw) {\n case 'STOP':\n return 'end_turn'\n case 'MAX_TOKENS':\n return 'max_tokens'\n case 'SAFETY':\n case 'RECITATION':\n case 'OTHER':\n return 'stop_sequence'\n default:\n return raw ?? 'end_turn'\n }\n}\n\n// Auto-register at module load\nregisterAIProvider('gemini', config => new GeminiAIClient(config))\n"]}
1
+ {"version":3,"file":"gemini.js","sourceRoot":"","sources":["../../../server/service/providers/gemini.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Google Gemini AI Client adapter.\n *\n * 기본 모델: gemini-3-pro (구성에서 override 가능).\n * SDK: @google/generative-ai\n *\n * 비고: Antigravity (Google 의 agentic IDE) 가 기본 백엔드로 사용하는 모델군.\n * 이 어댑터는 Gemini API 직접 호출 — Antigravity IDE 와는 별개.\n */\nimport { GoogleGenerativeAI } from '@google/generative-ai'\nimport type {\n AIChatOptions,\n AIClient,\n AIClientConfig,\n AIContentPart,\n AIJSONOptions,\n AIMessage,\n AITool,\n AIToolCall,\n AIToolChatOptions,\n AIToolChatResult,\n AIVisionOptions\n} from '../types.js'\nimport { registerAIProvider } from '../factory.js'\nimport { parseJSONResponse } from '../utils/parse-json.js'\n\n// `gemini-3-pro` 는 2026-03-09 종료됨. 후속 권장 모델로 변경.\n// 사용자가 자기 환경에서 활용 가능한 정확한 ID 는 ListModels 로 확인:\n// curl \"https://generativelanguage.googleapis.com/v1beta/models?key=$KEY\"\nconst DEFAULT_MODEL = 'gemini-3-flash-preview'\nconst DEFAULT_MAX_TOKENS = 8192\n\nexport class GeminiAIClient implements AIClient {\n readonly id: string\n private sdk: GoogleGenerativeAI\n private defaultModel: string\n private apiKey: string\n private baseURL?: string\n\n constructor(config: AIClientConfig) {\n this.apiKey = config.apiKey\n this.baseURL = config.baseURL\n this.sdk = new GoogleGenerativeAI(config.apiKey)\n this.defaultModel = config.model ?? DEFAULT_MODEL\n this.id = `gemini:${this.defaultModel}`\n }\n\n async chat(messages: AIMessage[], options: AIChatOptions = {}): Promise<string> {\n const { systemPrompt, contents } = splitMessages(messages, options.systemPrompt)\n const model = this.buildModel(options, systemPrompt)\n const result = await model.generateContent({ contents } as any)\n reportUsage(result, options, options.model ?? this.defaultModel)\n return result.response.text()\n }\n\n async generateJSON<T = unknown>(\n prompt: string | AIMessage[],\n options: AIJSONOptions = {}\n ): Promise<T> {\n const messages: AIMessage[] =\n typeof prompt === 'string' ? [{ role: 'user', content: prompt }] : prompt\n const { systemPrompt, contents } = splitMessages(messages, options.systemPrompt)\n\n const generationConfig: any = baseGenerationConfig(options)\n generationConfig.responseMimeType = 'application/json'\n if (options.schema) generationConfig.responseSchema = options.schema\n\n const model = this.sdk.getGenerativeModel({\n model: options.model ?? this.defaultModel,\n ...(systemPrompt && { systemInstruction: systemPrompt }),\n generationConfig\n }, this.requestOptions)\n const result = await model.generateContent({ contents } as any)\n reportUsage(result, options, options.model ?? this.defaultModel)\n const text = result.response.text()\n return parseJSONResponse<T>(text, { cutShort: result.response.candidates?.[0]?.finishReason === 'MAX_TOKENS' })\n }\n\n async vision(\n image: Buffer | Uint8Array | { url: string },\n prompt: string,\n options: AIVisionOptions = {}\n ): Promise<string> {\n const imagePart: AIContentPart =\n typeof image === 'object' && 'url' in image\n ? { type: 'image', source: { kind: 'url', url: image.url } }\n : {\n type: 'image',\n source: { kind: 'base64', data: image as Buffer | Uint8Array, mediaType: 'image/png' }\n }\n\n return this.chat(\n [{ role: 'user', content: [imagePart, { type: 'text', text: prompt }] }],\n options\n )\n }\n\n private buildModel(options: AIChatOptions, systemInstruction?: string) {\n return this.sdk.getGenerativeModel({\n model: options.model ?? this.defaultModel,\n ...(systemInstruction && { systemInstruction }),\n generationConfig: baseGenerationConfig(options)\n }, this.requestOptions)\n }\n\n async chatWithTools(\n messages: AIMessage[],\n tools: AITool[],\n options: AIToolChatOptions = {}\n ): Promise<AIToolChatResult> {\n const { systemPrompt, contents } = splitMessages(messages, options.systemPrompt)\n\n // Gemini 는 OpenAPI 3.0 subset — JSON Schema 의 일부 키를 거부함.\n // 'additionalProperties' / '$schema' / '$defs' 등을 제거.\n const functionDeclarations = tools.map(t => ({\n name: t.name,\n description: t.description,\n parameters: sanitizeForGeminiSchema(t.parameters)\n }))\n\n const toolConfig = buildGeminiToolConfig(options)\n\n const model = this.sdk.getGenerativeModel({\n model: options.model ?? this.defaultModel,\n ...(systemPrompt && { systemInstruction: systemPrompt }),\n generationConfig: baseGenerationConfig(options),\n tools: [{ functionDeclarations }] as any,\n ...(toolConfig && { toolConfig })\n } as any, this.requestOptions)\n\n const result = await model.generateContent({ contents } as any)\n reportUsage(result, options, options.model ?? this.defaultModel)\n const response: any = result.response\n\n const candidate = response?.candidates?.[0]\n const parts = candidate?.content?.parts ?? []\n\n // Gemini 는 한 응답에서 (a) thinking part (p.thought === true) 와\n // (b) 본문 text part 를 함께 emit 할 수 있다. thought 는 사용자에게 노출하면\n // \"구조화 답변 + 산문 답변\" 형태의 중복으로 보이므로 제거.\n const textParts = parts\n .filter((p: any) => typeof p?.text === 'string' && p?.thought !== true)\n .map((p: any) => p.text)\n const text = textParts.join('') || undefined\n\n // Gemini 는 functionCall 에 자체 id 가 없음 — 어댑터가 turn 내부에서 부여.\n // id 포맷 'g:<name>:<seq>' — assistant 가 tool_result 를 만들 때 이 id 를 그대로\n // 넘기면 toGeminiPart 가 name 을 추출해 functionResponse.name 에 채운다.\n //\n // thoughtSignature: Gemini 2.5/3 부터 thinking 모델이 functionCall 에 서명을 붙임.\n // 다음 turn 에 그대로 echo 해야 400 에러 안 남 (cite: ai.google.dev/.../thought-signatures).\n let geminiCallSeq = 0\n const ts = Date.now()\n const toolCalls: AIToolCall[] = parts\n .filter((p: any) => p?.functionCall && typeof p.functionCall.name === 'string')\n .map((p: any) => {\n // SDK 가 camelCase 또는 snake_case 둘 중 하나로 emit — 둘 다 받음.\n const sig: string | undefined = p.thoughtSignature ?? p.thought_signature\n return {\n id: `g:${p.functionCall.name}:${ts}_${geminiCallSeq++}`,\n name: p.functionCall.name,\n arguments:\n p.functionCall.args && typeof p.functionCall.args === 'object'\n ? (p.functionCall.args as Record<string, any>)\n : {},\n ...(typeof sig === 'string' && sig.length > 0 && {\n providerMeta: { thoughtSignature: sig }\n })\n }\n })\n\n return {\n text,\n toolCalls,\n stopReason: normalizeGeminiStopReason(candidate?.finishReason, toolCalls.length > 0)\n }\n }\n\n /**\n * The SDK has no model list, so this reads the REST one. A conversation model is one that answers\n * generateContent; embedding and other models are left out.\n */\n async listModels(): Promise<string[]> {\n const base = (this.baseURL || 'https://generativelanguage.googleapis.com').replace(/\\/+$/, '')\n const ids: string[] = []\n let pageToken = ''\n do {\n const url = `${base}/v1beta/models?pageSize=1000${pageToken ? `&pageToken=${encodeURIComponent(pageToken)}` : ''}`\n const response = await fetch(url, { headers: { 'x-goog-api-key': this.apiKey } })\n if (!response.ok) throw new ProviderListError(response.status, await response.text())\n const page: any = await response.json()\n for (const model of page.models ?? []) {\n if ((model.supportedGenerationMethods ?? []).includes('generateContent')) {\n ids.push(String(model.name).replace(/^models\\//, ''))\n }\n }\n pageToken = page.nextPageToken ?? ''\n } while (pageToken)\n return ids.sort()\n }\n\n private get requestOptions() {\n return this.baseURL ? { baseUrl: this.baseURL } : undefined\n }\n}\n\n/** A model list the provider refused, with the HTTP status the caller turns into a reason. */\nexport class ProviderListError extends Error {\n constructor(readonly status: number, body: string) {\n super(`model list failed with ${status}: ${body.slice(0, 200)}`)\n }\n}\n\nfunction reportUsage(result: any, options: AIChatOptions, requested: string): void {\n const usage = result?.response?.usageMetadata\n options.onUsage?.({\n model: result?.response?.modelVersion ?? requested,\n inputTokens: usage?.promptTokenCount,\n outputTokens: usage?.candidatesTokenCount\n })\n}\n\n// ── 변환 유틸 ─────────────────────────────────────────────────────────\n\nfunction baseGenerationConfig(options: AIChatOptions): any {\n return {\n maxOutputTokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n ...(options.stopSequences && { stopSequences: options.stopSequences })\n }\n}\n\nfunction splitMessages(\n messages: AIMessage[],\n externalSystem?: string\n): { systemPrompt: string; contents: any[] } {\n const systemMessages = messages\n .filter(m => m.role === 'system')\n .map(extractTextContent)\n .filter(Boolean)\n const conversation = messages.filter(m => m.role !== 'system')\n const systemPrompt = [externalSystem, ...systemMessages].filter(Boolean).join('\\n\\n')\n return { systemPrompt, contents: conversation.map(toGeminiContent) }\n}\n\nfunction extractTextContent(msg: AIMessage): string {\n if (typeof msg.content === 'string') return msg.content\n return msg.content\n .filter(p => p.type === 'text')\n .map(p => (p as { type: 'text'; text: string }).text)\n .join('\\n')\n}\n\nfunction toGeminiContent(msg: AIMessage): any {\n // Our 'assistant' role → Gemini's 'model' role\n const role = msg.role === 'assistant' ? 'model' : msg.role\n const parts =\n typeof msg.content === 'string'\n ? [{ text: msg.content }]\n : msg.content.map(toGeminiPart)\n return { role, parts }\n}\n\nfunction toGeminiPart(part: AIContentPart): any {\n if (part.type === 'text') {\n return { text: part.text }\n }\n if (part.type === 'tool_use') {\n // Gemini: assistant 가 호출 의도 표현. functionCall.name + args 만 가짐 (id 없음).\n // thoughtSignature 는 thinking 모델 사용 시 필수 — providerMeta 에서 복원해 echo.\n const sig = part.providerMeta?.thoughtSignature\n return {\n functionCall: { name: part.name, args: part.arguments },\n ...(typeof sig === 'string' && sig.length > 0 && { thoughtSignature: sig })\n }\n }\n if (part.type === 'tool_result') {\n // Gemini: user role 에 functionResponse part 로 회신. 이름은 호출했던 tool name 과\n // 같아야 함 — assistant.ts 의 loop 에서 part 생성 시 함께 전달.\n let parsed: any = part.content\n try {\n parsed = JSON.parse(part.content)\n } catch {\n parsed = { result: part.content }\n }\n return {\n functionResponse: {\n // name 은 원래 호출했던 tool name. toolUseId 에 임베드되어 있지 않으므로\n // assistant 가 메시지 생성 시 별도 채워줘야 함 — 여기서는 placeholder.\n // 실제 사용 시 toolUseId 를 'tool_<name>_<seq>' 형태로 만들어 name 추출.\n name: extractGeminiNameFromId(part.toolUseId),\n response: parsed\n }\n }\n }\n // image\n if (part.source.kind === 'url') {\n // Gemini SDK 는 inlineData(base64) 만 지원. URL 은 호출 전에 fetch 필요.\n throw new Error(\n 'Gemini adapter: image URL is not supported directly. Fetch the URL and pass as Buffer.'\n )\n }\n const data = Buffer.isBuffer(part.source.data)\n ? part.source.data.toString('base64')\n : Buffer.from(part.source.data).toString('base64')\n return {\n inlineData: { data, mimeType: part.source.mediaType }\n }\n}\n\n/**\n * Gemini 의 functionCall 에는 자체 id 가 없어서 chatWithTools 가 'gemini_<ts>_<seq>'\n * 형태의 가짜 id 를 만든다. 그런데 functionResponse 에는 name 이 필수 — assistant 의\n * tool 실행기는 이 id 와 name 을 별도 매핑해야 한다. assistant 가 tool_result part 를\n * 만들 때 toolUseId 에 'name:<actualName>;id:<originalId>' 같은 인코딩을 할 수 있지만\n * 그건 invasive — 대신 toolUseId 가 'g:<name>:<id>' 형태면 name 을 직접 추출.\n *\n * 현재는 fallback: id 가 'g:' 로 시작하면 두 번째 segment 를 name 으로,\n * 아니면 그대로 사용 (Gemini 가 받지 않으면 에러 — assistant 가 책임지고 인코딩).\n */\nfunction extractGeminiNameFromId(id: string): string {\n if (id.startsWith('g:')) {\n const parts = id.split(':')\n return parts[1] || id\n }\n return id\n}\n\n/**\n * JSON Schema → Gemini OpenAPI 3.0 subset 변환.\n * Gemini API 가 거부하는 키들 (deep) 제거.\n * 보존: type / properties / items / required / enum / description / format / nullable\n */\nconst GEMINI_REJECTED_KEYS = new Set([\n 'additionalProperties',\n '$schema',\n '$defs',\n '$ref',\n '$id',\n 'definitions',\n 'patternProperties',\n 'dependencies'\n])\n\nfunction sanitizeForGeminiSchema(schema: any): any {\n if (schema === null || schema === undefined) return schema\n if (Array.isArray(schema)) return schema.map(sanitizeForGeminiSchema)\n if (typeof schema !== 'object') return schema\n const out: any = {}\n for (const [k, v] of Object.entries(schema)) {\n if (GEMINI_REJECTED_KEYS.has(k)) continue\n out[k] = sanitizeForGeminiSchema(v)\n }\n return out\n}\n\nfunction buildGeminiToolConfig(options: AIToolChatOptions): any | undefined {\n const choice = options.toolChoice ?? 'auto'\n if (choice === 'none') {\n return { functionCallingConfig: { mode: 'NONE' } }\n }\n if (choice === 'required') {\n return {\n functionCallingConfig: {\n mode: 'ANY',\n ...(options.forceTool && { allowedFunctionNames: [options.forceTool] })\n }\n }\n }\n return { functionCallingConfig: { mode: 'AUTO' } }\n}\n\nfunction normalizeGeminiStopReason(\n raw: any,\n hasToolCalls: boolean\n): AIToolChatResult['stopReason'] {\n // Gemini 는 function call 발생 시 finishReason 을 'STOP' 으로 마킹할 수 있어\n // toolCalls 존재 여부로 우선 판정.\n if (hasToolCalls) return 'tool_use'\n switch (raw) {\n case 'STOP':\n return 'end_turn'\n case 'MAX_TOKENS':\n return 'max_tokens'\n case 'SAFETY':\n case 'RECITATION':\n case 'OTHER':\n return 'stop_sequence'\n default:\n return raw ?? 'end_turn'\n }\n}\n\n// Auto-register at module load\nregisterAIProvider('gemini', config => new GeminiAIClient(config))\n"]}
@@ -59,7 +59,7 @@ class OpenAIAIClient {
59
59
  });
60
60
  reportUsage(response, options, options.model ?? this.defaultModel);
61
61
  const text = response.choices[0]?.message?.content ?? '{}';
62
- return (0, parse_json_js_1.parseJSONResponse)(text);
62
+ return (0, parse_json_js_1.parseJSONResponse)(text, { cutShort: response.choices[0]?.finish_reason === 'length' });
63
63
  }
64
64
  async vision(image, prompt, options = {}) {
65
65
  const imagePart = typeof image === 'object' && 'url' in image
@@ -1 +1 @@
1
- {"version":3,"file":"openai.js","sourceRoot":"","sources":["../../../server/service/providers/openai.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OpenAI AI Client adapter.\n *\n * 기본 모델: gpt-4o (구성에서 override 가능).\n * SDK: openai\n *\n * organization 필드 지원.\n */\nimport OpenAI from 'openai'\nimport type {\n AIChatOptions,\n AIClient,\n AIClientConfig,\n AIContentPart,\n AIJSONOptions,\n AIMessage,\n AITool,\n AIToolCall,\n AIToolChatOptions,\n AIToolChatResult,\n AIVisionOptions\n} from '../types.js'\nimport { registerAIProvider } from '../factory.js'\nimport { parseJSONResponse } from '../utils/parse-json.js'\n\nconst DEFAULT_MODEL = 'gpt-4o'\nconst DEFAULT_MAX_TOKENS = 4096\n\nexport class OpenAIAIClient implements AIClient {\n readonly id: string\n private sdk: OpenAI\n private defaultModel: string\n\n constructor(config: AIClientConfig) {\n this.sdk = new OpenAI({\n apiKey: config.apiKey,\n ...(config.organization && { organization: config.organization }),\n ...(config.baseURL && { baseURL: config.baseURL })\n })\n this.defaultModel = config.model ?? DEFAULT_MODEL\n this.id = `openai:${this.defaultModel}`\n }\n\n async chat(messages: AIMessage[], options: AIChatOptions = {}): Promise<string> {\n const sdkMessages = buildSDKMessages(messages, options.systemPrompt)\n const response = await this.sdk.chat.completions.create({\n model: options.model ?? this.defaultModel,\n /* `max_tokens` is refused by the gpt-5 and o-series models; every chat model takes this one. */\n max_completion_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n ...(options.stopSequences && { stop: options.stopSequences }),\n messages: sdkMessages as any\n })\n reportUsage(response, options, options.model ?? this.defaultModel)\n return response.choices[0]?.message?.content ?? ''\n }\n\n async generateJSON<T = unknown>(\n prompt: string | AIMessage[],\n options: AIJSONOptions = {}\n ): Promise<T> {\n const messages: AIMessage[] =\n typeof prompt === 'string' ? [{ role: 'user', content: prompt }] : prompt\n\n const schemaHint = options.schema\n ? `\\n\\nResponse MUST conform to this JSON schema:\\n${JSON.stringify(options.schema)}`\n : ''\n const enforcedSystem =\n (options.systemPrompt ? options.systemPrompt + '\\n\\n' : '') +\n 'Return only valid JSON. No markdown code fences, no commentary.' +\n schemaHint\n\n const sdkMessages = buildSDKMessages(messages, enforcedSystem)\n const response = await this.sdk.chat.completions.create({\n model: options.model ?? this.defaultModel,\n /* `max_tokens` is refused by the gpt-5 and o-series models; every chat model takes this one. */\n max_completion_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n response_format: { type: 'json_object' },\n messages: sdkMessages as any\n })\n reportUsage(response, options, options.model ?? this.defaultModel)\n const text = response.choices[0]?.message?.content ?? '{}'\n return parseJSONResponse<T>(text)\n }\n\n async vision(\n image: Buffer | Uint8Array | { url: string },\n prompt: string,\n options: AIVisionOptions = {}\n ): Promise<string> {\n const imagePart: AIContentPart =\n typeof image === 'object' && 'url' in image\n ? { type: 'image', source: { kind: 'url', url: image.url } }\n : {\n type: 'image',\n source: { kind: 'base64', data: image as Buffer | Uint8Array, mediaType: 'image/png' }\n }\n\n return this.chat(\n [{ role: 'user', content: [imagePart, { type: 'text', text: prompt }] }],\n options\n )\n }\n\n async chatWithTools(\n messages: AIMessage[],\n tools: AITool[],\n options: AIToolChatOptions = {}\n ): Promise<AIToolChatResult> {\n const sdkMessages = buildSDKMessages(messages, options.systemPrompt)\n\n const sdkTools = tools.map(t => ({\n type: 'function' as const,\n function: {\n name: t.name,\n description: t.description,\n parameters: t.parameters\n }\n }))\n\n const toolChoice = buildOpenAIToolChoice(options)\n\n const response = await this.sdk.chat.completions.create({\n model: options.model ?? this.defaultModel,\n /* `max_tokens` is refused by the gpt-5 and o-series models; every chat model takes this one. */\n max_completion_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n ...(options.stopSequences && { stop: options.stopSequences }),\n messages: sdkMessages as any,\n tools: sdkTools,\n ...(toolChoice !== undefined && { tool_choice: toolChoice as any }),\n ...(options.allowParallelToolCalls === false && { parallel_tool_calls: false })\n })\n reportUsage(response, options, options.model ?? this.defaultModel)\n\n const choice = response.choices?.[0]\n const message = choice?.message\n const text = message?.content || undefined\n\n const toolCalls: AIToolCall[] = (message?.tool_calls || []).map((tc: any) => {\n const fn = tc.function\n let args: Record<string, any> = {}\n try {\n args = fn?.arguments ? JSON.parse(fn.arguments) : {}\n } catch {\n args = {}\n }\n return {\n id: typeof tc.id === 'string' && tc.id ? tc.id : `call_${Math.random().toString(36).slice(2, 12)}`,\n name: fn?.name ?? '',\n arguments: args\n }\n })\n\n return {\n text,\n toolCalls,\n stopReason: normalizeOpenAIStopReason(choice?.finish_reason)\n }\n }\n\n async listModels(): Promise<string[]> {\n const ids: string[] = []\n for await (const model of this.sdk.models.list()) ids.push(model.id)\n return ids.filter(isOpenAIChatModel).sort()\n }\n}\n\n/**\n * OpenAI's model list has no capability field, so the conversation models are told apart by name: the GPT and\n * o-series families, without their audio, realtime, speech, image, search and instruct variants. One rule, kept by a test.\n */\nexport function isOpenAIChatModel(id: string): boolean {\n if (!/^(gpt-|chatgpt-|o\\d)/.test(id)) return false\n return !/(audio|realtime|tts|transcribe|image|search|instruct|embedding|moderation)/.test(id)\n}\n\nfunction reportUsage(response: any, options: AIChatOptions, requested: string): void {\n options.onUsage?.({\n model: response?.model ?? requested,\n inputTokens: response?.usage?.prompt_tokens,\n outputTokens: response?.usage?.completion_tokens\n })\n}\n\n// ── 변환 유틸 ─────────────────────────────────────────────────────────\n\nfunction buildSDKMessages(messages: AIMessage[], externalSystem?: string): any[] {\n const out: any[] = []\n\n const systemFromMessages = messages\n .filter(m => m.role === 'system')\n .map(extractTextContent)\n .filter(Boolean)\n .join('\\n\\n')\n const combinedSystem = [externalSystem, systemFromMessages].filter(Boolean).join('\\n\\n')\n if (combinedSystem) {\n out.push({ role: 'system', content: combinedSystem })\n }\n\n for (const msg of messages) {\n if (msg.role === 'system') continue\n const sdkMsgs = toSDKMessages(msg)\n out.push(...sdkMsgs)\n }\n\n return out\n}\n\n/**\n * 한 AIMessage 가 다중 tool_result 를 가지면 OpenAI 측에서 여러 role:'tool' 메시지로\n * 분리해야 함 (OpenAI 는 메시지당 tool_call_id 하나만 매핑). 따라서 array 를 반환.\n */\nexport function toSDKMessages(msg: AIMessage): any[] {\n if (typeof msg.content === 'string') {\n return [{ role: msg.role, content: msg.content }]\n }\n\n const parts = msg.content\n const toolUseParts = parts.filter(p => p.type === 'tool_use') as any[]\n const toolResultParts = parts.filter(p => p.type === 'tool_result') as any[]\n const otherParts = parts.filter(p => p.type !== 'tool_use' && p.type !== 'tool_result')\n\n // tool_result 가 있으면 각각을 role='tool' 메시지로 분리 (OpenAI 요구사항).\n if (toolResultParts.length > 0) {\n const results = toolResultParts.map((r: any) => ({\n role: 'tool',\n tool_call_id: r.toolUseId,\n content: r.content\n }))\n /* A tool message holds text only - pictures a read tool returned follow as one user message (agentic-loop takeToolImages). */\n const images = otherParts.filter(p => p.type === 'image')\n return images.length ? [...results, { role: 'user', content: images.map(toSDKContentPart) }] : results\n }\n\n // assistant 측 tool_use 가 있으면 message.tool_calls 로\n const out: any = { role: msg.role }\n if (otherParts.length > 0) {\n out.content = otherParts.map(toSDKContentPart)\n } else {\n out.content = null\n }\n if (toolUseParts.length > 0) {\n out.tool_calls = toolUseParts.map(p => ({\n id: p.id,\n type: 'function',\n function: { name: p.name, arguments: JSON.stringify(p.arguments) }\n }))\n }\n return [out]\n}\n\nfunction toSDKContentPart(part: AIContentPart): any {\n if (part.type === 'text') {\n return { type: 'text', text: part.text }\n }\n if (part.type === 'tool_use' || part.type === 'tool_result') {\n // 처리는 toSDKMessage 가 함 — 도달하면 버그.\n return { type: 'text', text: '' }\n }\n // image\n if (part.source.kind === 'url') {\n return { type: 'image_url', image_url: { url: part.source.url } }\n }\n const b64 = Buffer.isBuffer(part.source.data)\n ? part.source.data.toString('base64')\n : Buffer.from(part.source.data).toString('base64')\n return {\n type: 'image_url',\n image_url: { url: `data:${part.source.mediaType};base64,${b64}` }\n }\n}\n\nfunction extractTextContent(msg: AIMessage): string {\n if (typeof msg.content === 'string') return msg.content\n return msg.content\n .filter(p => p.type === 'text')\n .map(p => (p as { type: 'text'; text: string }).text)\n .join('\\n')\n}\n\nfunction buildOpenAIToolChoice(options: AIToolChatOptions): any {\n const choice = options.toolChoice ?? 'auto'\n if (choice === 'none') return 'none'\n if (choice === 'required') {\n if (options.forceTool) {\n return { type: 'function', function: { name: options.forceTool } }\n }\n return 'required'\n }\n return 'auto'\n}\n\nfunction normalizeOpenAIStopReason(raw: any): AIToolChatResult['stopReason'] {\n switch (raw) {\n case 'tool_calls':\n return 'tool_use'\n case 'function_call':\n return 'tool_use'\n case 'stop':\n return 'end_turn'\n case 'length':\n return 'max_tokens'\n case 'content_filter':\n return 'stop_sequence'\n default:\n return raw ?? 'end_turn'\n }\n}\n\n// Auto-register at module load\nregisterAIProvider('openai', config => new OpenAIAIClient(config))\n"]}
1
+ {"version":3,"file":"openai.js","sourceRoot":"","sources":["../../../server/service/providers/openai.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OpenAI AI Client adapter.\n *\n * 기본 모델: gpt-4o (구성에서 override 가능).\n * SDK: openai\n *\n * organization 필드 지원.\n */\nimport OpenAI from 'openai'\nimport type {\n AIChatOptions,\n AIClient,\n AIClientConfig,\n AIContentPart,\n AIJSONOptions,\n AIMessage,\n AITool,\n AIToolCall,\n AIToolChatOptions,\n AIToolChatResult,\n AIVisionOptions\n} from '../types.js'\nimport { registerAIProvider } from '../factory.js'\nimport { parseJSONResponse } from '../utils/parse-json.js'\n\nconst DEFAULT_MODEL = 'gpt-4o'\nconst DEFAULT_MAX_TOKENS = 4096\n\nexport class OpenAIAIClient implements AIClient {\n readonly id: string\n private sdk: OpenAI\n private defaultModel: string\n\n constructor(config: AIClientConfig) {\n this.sdk = new OpenAI({\n apiKey: config.apiKey,\n ...(config.organization && { organization: config.organization }),\n ...(config.baseURL && { baseURL: config.baseURL })\n })\n this.defaultModel = config.model ?? DEFAULT_MODEL\n this.id = `openai:${this.defaultModel}`\n }\n\n async chat(messages: AIMessage[], options: AIChatOptions = {}): Promise<string> {\n const sdkMessages = buildSDKMessages(messages, options.systemPrompt)\n const response = await this.sdk.chat.completions.create({\n model: options.model ?? this.defaultModel,\n /* `max_tokens` is refused by the gpt-5 and o-series models; every chat model takes this one. */\n max_completion_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n ...(options.stopSequences && { stop: options.stopSequences }),\n messages: sdkMessages as any\n })\n reportUsage(response, options, options.model ?? this.defaultModel)\n return response.choices[0]?.message?.content ?? ''\n }\n\n async generateJSON<T = unknown>(\n prompt: string | AIMessage[],\n options: AIJSONOptions = {}\n ): Promise<T> {\n const messages: AIMessage[] =\n typeof prompt === 'string' ? [{ role: 'user', content: prompt }] : prompt\n\n const schemaHint = options.schema\n ? `\\n\\nResponse MUST conform to this JSON schema:\\n${JSON.stringify(options.schema)}`\n : ''\n const enforcedSystem =\n (options.systemPrompt ? options.systemPrompt + '\\n\\n' : '') +\n 'Return only valid JSON. No markdown code fences, no commentary.' +\n schemaHint\n\n const sdkMessages = buildSDKMessages(messages, enforcedSystem)\n const response = await this.sdk.chat.completions.create({\n model: options.model ?? this.defaultModel,\n /* `max_tokens` is refused by the gpt-5 and o-series models; every chat model takes this one. */\n max_completion_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n response_format: { type: 'json_object' },\n messages: sdkMessages as any\n })\n reportUsage(response, options, options.model ?? this.defaultModel)\n const text = response.choices[0]?.message?.content ?? '{}'\n return parseJSONResponse<T>(text, { cutShort: response.choices[0]?.finish_reason === 'length' })\n }\n\n async vision(\n image: Buffer | Uint8Array | { url: string },\n prompt: string,\n options: AIVisionOptions = {}\n ): Promise<string> {\n const imagePart: AIContentPart =\n typeof image === 'object' && 'url' in image\n ? { type: 'image', source: { kind: 'url', url: image.url } }\n : {\n type: 'image',\n source: { kind: 'base64', data: image as Buffer | Uint8Array, mediaType: 'image/png' }\n }\n\n return this.chat(\n [{ role: 'user', content: [imagePart, { type: 'text', text: prompt }] }],\n options\n )\n }\n\n async chatWithTools(\n messages: AIMessage[],\n tools: AITool[],\n options: AIToolChatOptions = {}\n ): Promise<AIToolChatResult> {\n const sdkMessages = buildSDKMessages(messages, options.systemPrompt)\n\n const sdkTools = tools.map(t => ({\n type: 'function' as const,\n function: {\n name: t.name,\n description: t.description,\n parameters: t.parameters\n }\n }))\n\n const toolChoice = buildOpenAIToolChoice(options)\n\n const response = await this.sdk.chat.completions.create({\n model: options.model ?? this.defaultModel,\n /* `max_tokens` is refused by the gpt-5 and o-series models; every chat model takes this one. */\n max_completion_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n ...(options.stopSequences && { stop: options.stopSequences }),\n messages: sdkMessages as any,\n tools: sdkTools,\n ...(toolChoice !== undefined && { tool_choice: toolChoice as any }),\n ...(options.allowParallelToolCalls === false && { parallel_tool_calls: false })\n })\n reportUsage(response, options, options.model ?? this.defaultModel)\n\n const choice = response.choices?.[0]\n const message = choice?.message\n const text = message?.content || undefined\n\n const toolCalls: AIToolCall[] = (message?.tool_calls || []).map((tc: any) => {\n const fn = tc.function\n let args: Record<string, any> = {}\n try {\n args = fn?.arguments ? JSON.parse(fn.arguments) : {}\n } catch {\n args = {}\n }\n return {\n id: typeof tc.id === 'string' && tc.id ? tc.id : `call_${Math.random().toString(36).slice(2, 12)}`,\n name: fn?.name ?? '',\n arguments: args\n }\n })\n\n return {\n text,\n toolCalls,\n stopReason: normalizeOpenAIStopReason(choice?.finish_reason)\n }\n }\n\n async listModels(): Promise<string[]> {\n const ids: string[] = []\n for await (const model of this.sdk.models.list()) ids.push(model.id)\n return ids.filter(isOpenAIChatModel).sort()\n }\n}\n\n/**\n * OpenAI's model list has no capability field, so the conversation models are told apart by name: the GPT and\n * o-series families, without their audio, realtime, speech, image, search and instruct variants. One rule, kept by a test.\n */\nexport function isOpenAIChatModel(id: string): boolean {\n if (!/^(gpt-|chatgpt-|o\\d)/.test(id)) return false\n return !/(audio|realtime|tts|transcribe|image|search|instruct|embedding|moderation)/.test(id)\n}\n\nfunction reportUsage(response: any, options: AIChatOptions, requested: string): void {\n options.onUsage?.({\n model: response?.model ?? requested,\n inputTokens: response?.usage?.prompt_tokens,\n outputTokens: response?.usage?.completion_tokens\n })\n}\n\n// ── 변환 유틸 ─────────────────────────────────────────────────────────\n\nfunction buildSDKMessages(messages: AIMessage[], externalSystem?: string): any[] {\n const out: any[] = []\n\n const systemFromMessages = messages\n .filter(m => m.role === 'system')\n .map(extractTextContent)\n .filter(Boolean)\n .join('\\n\\n')\n const combinedSystem = [externalSystem, systemFromMessages].filter(Boolean).join('\\n\\n')\n if (combinedSystem) {\n out.push({ role: 'system', content: combinedSystem })\n }\n\n for (const msg of messages) {\n if (msg.role === 'system') continue\n const sdkMsgs = toSDKMessages(msg)\n out.push(...sdkMsgs)\n }\n\n return out\n}\n\n/**\n * 한 AIMessage 가 다중 tool_result 를 가지면 OpenAI 측에서 여러 role:'tool' 메시지로\n * 분리해야 함 (OpenAI 는 메시지당 tool_call_id 하나만 매핑). 따라서 array 를 반환.\n */\nexport function toSDKMessages(msg: AIMessage): any[] {\n if (typeof msg.content === 'string') {\n return [{ role: msg.role, content: msg.content }]\n }\n\n const parts = msg.content\n const toolUseParts = parts.filter(p => p.type === 'tool_use') as any[]\n const toolResultParts = parts.filter(p => p.type === 'tool_result') as any[]\n const otherParts = parts.filter(p => p.type !== 'tool_use' && p.type !== 'tool_result')\n\n // tool_result 가 있으면 각각을 role='tool' 메시지로 분리 (OpenAI 요구사항).\n if (toolResultParts.length > 0) {\n const results = toolResultParts.map((r: any) => ({\n role: 'tool',\n tool_call_id: r.toolUseId,\n content: r.content\n }))\n /* A tool message holds text only - pictures a read tool returned follow as one user message (agentic-loop takeToolImages). */\n const images = otherParts.filter(p => p.type === 'image')\n return images.length ? [...results, { role: 'user', content: images.map(toSDKContentPart) }] : results\n }\n\n // assistant 측 tool_use 가 있으면 message.tool_calls 로\n const out: any = { role: msg.role }\n if (otherParts.length > 0) {\n out.content = otherParts.map(toSDKContentPart)\n } else {\n out.content = null\n }\n if (toolUseParts.length > 0) {\n out.tool_calls = toolUseParts.map(p => ({\n id: p.id,\n type: 'function',\n function: { name: p.name, arguments: JSON.stringify(p.arguments) }\n }))\n }\n return [out]\n}\n\nfunction toSDKContentPart(part: AIContentPart): any {\n if (part.type === 'text') {\n return { type: 'text', text: part.text }\n }\n if (part.type === 'tool_use' || part.type === 'tool_result') {\n // 처리는 toSDKMessage 가 함 — 도달하면 버그.\n return { type: 'text', text: '' }\n }\n // image\n if (part.source.kind === 'url') {\n return { type: 'image_url', image_url: { url: part.source.url } }\n }\n const b64 = Buffer.isBuffer(part.source.data)\n ? part.source.data.toString('base64')\n : Buffer.from(part.source.data).toString('base64')\n return {\n type: 'image_url',\n image_url: { url: `data:${part.source.mediaType};base64,${b64}` }\n }\n}\n\nfunction extractTextContent(msg: AIMessage): string {\n if (typeof msg.content === 'string') return msg.content\n return msg.content\n .filter(p => p.type === 'text')\n .map(p => (p as { type: 'text'; text: string }).text)\n .join('\\n')\n}\n\nfunction buildOpenAIToolChoice(options: AIToolChatOptions): any {\n const choice = options.toolChoice ?? 'auto'\n if (choice === 'none') return 'none'\n if (choice === 'required') {\n if (options.forceTool) {\n return { type: 'function', function: { name: options.forceTool } }\n }\n return 'required'\n }\n return 'auto'\n}\n\nfunction normalizeOpenAIStopReason(raw: any): AIToolChatResult['stopReason'] {\n switch (raw) {\n case 'tool_calls':\n return 'tool_use'\n case 'function_call':\n return 'tool_use'\n case 'stop':\n return 'end_turn'\n case 'length':\n return 'max_tokens'\n case 'content_filter':\n return 'stop_sequence'\n default:\n return raw ?? 'end_turn'\n }\n}\n\n// Auto-register at module load\nregisterAIProvider('openai', config => new OpenAIAIClient(config))\n"]}
@@ -1,4 +1,4 @@
1
- import type { ToolCallContext, ToolCallMode } from '../tool-registry/types.js';
1
+ import type { ToolAttachment, ToolCallContext, ToolCallMode, ToolTranslate } from '../tool-registry/types.js';
2
2
  export interface ToolCallContextFixtureInput {
3
3
  domainId?: string;
4
4
  userId?: string;
@@ -6,5 +6,9 @@ export interface ToolCallContextFixtureInput {
6
6
  state?: Record<string, any>;
7
7
  host?: Record<string, any>;
8
8
  mode?: ToolCallMode;
9
+ /** The request translator. Absent: the one the door hands when it was given none. */
10
+ t?: ToolTranslate;
11
+ /** The checked files of the turn. Absent: none. */
12
+ attachments?: ToolAttachment[];
9
13
  }
10
14
  export declare function toolCallContextFixture(input?: ToolCallContextFixtureInput): ToolCallContext;
@@ -1,10 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.toolCallContextFixture = toolCallContextFixture;
4
+ const translate_js_1 = require("../tool-registry/translate.js");
4
5
  function toolCallContextFixture(input = {}) {
5
6
  const context = {
6
7
  state: { domain: { id: input.domainId ?? 'domain-test' }, user: { id: input.userId ?? 'user-test' }, ...(input.state ?? {}) },
7
- host: input.host ?? {}
8
+ host: input.host ?? {},
9
+ t: input.t ?? translate_js_1.defaultValueTranslate,
10
+ attachments: input.attachments ?? []
8
11
  };
9
12
  if (input.mode)
10
13
  context.mode = input.mode;
@@ -1 +1 @@
1
- {"version":3,"file":"tool-call-context.js","sourceRoot":"","sources":["../../../server/service/testing/tool-call-context.ts"],"names":[],"mappings":";;AAoBA,wDAOC;AAPD,SAAgB,sBAAsB,CAAC,QAAqC,EAAE;IAC5E,MAAM,OAAO,GAAoB;QAC/B,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,QAAQ,IAAI,aAAa,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,MAAM,IAAI,WAAW,EAAE,EAAE,GAAG,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE;QAC7H,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE;KACvB,CAAA;IACD,IAAI,KAAK,CAAC,IAAI;QAAE,OAAO,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAA;IACzC,OAAO,OAAO,CAAA;AAChB,CAAC","sourcesContent":["/*\n * The one way a test builds what a registered tool is handed (A3, 2026-09-17).\n *\n * The twin's space tools read `ctx.state.host` while the door passed `ctx.host`, and their tests\n * stayed green because each test wrote its own context in the shape the tool expected. A fixture\n * owned by each test agrees with its tool by construction. This one is owned by the package that\n * defines the neutral door, and that door's tests check it hands tools exactly this shape — so a tool\n * test built on it fails when the tool reads a field no door fills.\n */\nimport type { ToolCallContext, ToolCallMode } from '../tool-registry/types.js'\n\nexport interface ToolCallContextFixtureInput {\n domainId?: string\n userId?: string\n /** Extra request-state fields a tool reads (e.g. `tx`). Merged after domain and user. */\n state?: Record<string, any>\n host?: Record<string, any>\n mode?: ToolCallMode\n}\n\nexport function toolCallContextFixture(input: ToolCallContextFixtureInput = {}): ToolCallContext {\n const context: ToolCallContext = {\n state: { domain: { id: input.domainId ?? 'domain-test' }, user: { id: input.userId ?? 'user-test' }, ...(input.state ?? {}) },\n host: input.host ?? {}\n }\n if (input.mode) context.mode = input.mode\n return context\n}\n"]}
1
+ {"version":3,"file":"tool-call-context.js","sourceRoot":"","sources":["../../../server/service/testing/tool-call-context.ts"],"names":[],"mappings":";;AAyBA,wDASC;AAxBD,gEAAqE;AAerE,SAAgB,sBAAsB,CAAC,QAAqC,EAAE;IAC5E,MAAM,OAAO,GAAoB;QAC/B,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,QAAQ,IAAI,aAAa,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,MAAM,IAAI,WAAW,EAAE,EAAE,GAAG,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE;QAC7H,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE;QACtB,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,oCAAqB;QACnC,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,EAAE;KACrC,CAAA;IACD,IAAI,KAAK,CAAC,IAAI;QAAE,OAAO,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAA;IACzC,OAAO,OAAO,CAAA;AAChB,CAAC","sourcesContent":["/*\n * The one way a test builds what a registered tool is handed (A3, 2026-09-17).\n *\n * The twin's space tools read `ctx.state.host` while the door passed `ctx.host`, and their tests\n * stayed green because each test wrote its own context in the shape the tool expected. A fixture\n * owned by each test agrees with its tool by construction. This one is owned by the package that\n * defines the neutral door, and that door's tests check it hands tools exactly this shape — so a tool\n * test built on it fails when the tool reads a field no door fills.\n */\nimport type { ToolAttachment, ToolCallContext, ToolCallMode, ToolTranslate } from '../tool-registry/types.js'\nimport { defaultValueTranslate } from '../tool-registry/translate.js'\n\nexport interface ToolCallContextFixtureInput {\n domainId?: string\n userId?: string\n /** Extra request-state fields a tool reads (e.g. `tx`). Merged after domain and user. */\n state?: Record<string, any>\n host?: Record<string, any>\n mode?: ToolCallMode\n /** The request translator. Absent: the one the door hands when it was given none. */\n t?: ToolTranslate\n /** The checked files of the turn. Absent: none. */\n attachments?: ToolAttachment[]\n}\n\nexport function toolCallContextFixture(input: ToolCallContextFixtureInput = {}): ToolCallContext {\n const context: ToolCallContext = {\n state: { domain: { id: input.domainId ?? 'domain-test' }, user: { id: input.userId ?? 'user-test' }, ...(input.state ?? {}) },\n host: input.host ?? {},\n t: input.t ?? defaultValueTranslate,\n attachments: input.attachments ?? []\n }\n if (input.mode) context.mode = input.mode\n return context\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import type { ToolCallContext } from './types';
2
+ export interface ToolCallRecord {
3
+ tool: string;
4
+ /** The category the tool was offered from. */
5
+ category: string;
6
+ args: unknown;
7
+ /** What the tool returned - absent when it threw. A refusal it returned (`{ rejected: true }`) is here. */
8
+ result?: unknown;
9
+ /** Why it threw. */
10
+ error?: string;
11
+ durationMs: number;
12
+ }
13
+ export type ToolCallRecorder = (record: ToolCallRecord, context: ToolCallContext) => void | Promise<void>;
14
+ export declare function registerToolCallRecorder(recorder: ToolCallRecorder): void;
15
+ export declare function clearToolCallRecorders(): void;
16
+ export declare function recordToolCall(record: ToolCallRecord, context: ToolCallContext): Promise<void>;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerToolCallRecorder = registerToolCallRecorder;
4
+ exports.clearToolCallRecorders = clearToolCallRecorders;
5
+ exports.recordToolCall = recordToolCall;
6
+ const recorders = [];
7
+ function registerToolCallRecorder(recorder) {
8
+ if (!recorders.includes(recorder))
9
+ recorders.push(recorder);
10
+ }
11
+ function clearToolCallRecorders() {
12
+ recorders.length = 0;
13
+ }
14
+ async function recordToolCall(record, context) {
15
+ for (const recorder of recorders) {
16
+ try {
17
+ await recorder(record, context);
18
+ }
19
+ catch (e) {
20
+ console.warn(`[ai-client-base] a tool call recorder failed for ${record.tool}:`, e?.message ?? e);
21
+ }
22
+ }
23
+ }
24
+ //# sourceMappingURL=call-recorder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"call-recorder.js","sourceRoot":"","sources":["../../../server/service/tool-registry/call-recorder.ts"],"names":[],"mappings":";;AA8BA,4DAEC;AAED,wDAEC;AAED,wCAQC;AAlBD,MAAM,SAAS,GAAuB,EAAE,CAAA;AAExC,SAAgB,wBAAwB,CAAC,QAA0B;IACjE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;AAC7D,CAAC;AAED,SAAgB,sBAAsB;IACpC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAA;AACtB,CAAC;AAEM,KAAK,UAAU,cAAc,CAAC,MAAsB,EAAE,OAAwB;IACnF,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QACjC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,CAAC,oDAAoD,MAAM,CAAC,IAAI,GAAG,EAAG,CAAW,EAAE,OAAO,IAAI,CAAC,CAAC,CAAA;QAC9G,CAAC;IACH,CAAC;AACH,CAAC","sourcesContent":["/*\n * Every tool call of a registry conversation, handed to whoever writes them down.\n *\n * An application that keeps an AI audit (operato-office's AI 감사 기록) needs each call - which tool, with what, what\n * came back or why it failed - whoever registered the tool: its own tools, a read target's list query, a data entry\n * target's proposal. Wrapping each tool in the application left out the tools other packages declare, so the audit\n * missed the reads that mattered most (2026-10-16). The registry conversation runs every tool at one place and calls\n * the recorders there.\n *\n * A recorder that fails is said in the log and the call goes on - an audit that cannot be written never stops an\n * answer. A recorder decides for itself which conversations it writes down (`context.mode.kind`).\n */\nimport type { ToolCallContext } from './types'\n\nexport interface ToolCallRecord {\n tool: string\n /** The category the tool was offered from. */\n category: string\n args: unknown\n /** What the tool returned - absent when it threw. A refusal it returned (`{ rejected: true }`) is here. */\n result?: unknown\n /** Why it threw. */\n error?: string\n durationMs: number\n}\n\nexport type ToolCallRecorder = (record: ToolCallRecord, context: ToolCallContext) => void | Promise<void>\n\nconst recorders: ToolCallRecorder[] = []\n\nexport function registerToolCallRecorder(recorder: ToolCallRecorder): void {\n if (!recorders.includes(recorder)) recorders.push(recorder)\n}\n\nexport function clearToolCallRecorders(): void {\n recorders.length = 0\n}\n\nexport async function recordToolCall(record: ToolCallRecord, context: ToolCallContext): Promise<void> {\n for (const recorder of recorders) {\n try {\n await recorder(record, context)\n } catch (e) {\n console.warn(`[ai-client-base] a tool call recorder failed for ${record.tool}:`, (e as Error)?.message ?? e)\n }\n }\n}\n"]}
@@ -4,5 +4,8 @@
4
4
  * 자세한 설계는 ./types 와 ./registry 의 헤더 주석 참조.
5
5
  */
6
6
  export { registerToolCategory, getToolCategories, getAllToolSpecs, getToolSpecsFor, getCategoryGuidance, findToolSpec, getToolKind, hasTool, getCategorySpecs, clearToolRegistry, getToolRegistryWarnings } from './registry';
7
+ export { defaultValueTranslate } from './translate';
8
+ export { registerToolCallRecorder, clearToolCallRecorders, recordToolCall } from './call-recorder';
9
+ export type { ToolCallRecord, ToolCallRecorder } from './call-recorder';
7
10
  export type { ToolCategoryFilter, ToolRegistryWarning } from './registry';
8
- export type { ToolSpec, ToolSpecBase, ReadToolSpec, WriteToolSpec, ActionToolSpec, ExternalToolSpec, ToolCategory, ReadToolBuilder, WriteToolBuilder, ActionToolBuilder, ExternalToolBuilder, ReadToolContext, WriteToolContext, ExternalToolContext, ToolCallContext, ToolCallMode } from './types';
11
+ export type { ToolSpec, ToolSpecBase, ReadToolSpec, WriteToolSpec, ActionToolSpec, ExternalToolSpec, ToolCategory, ReadToolBuilder, WriteToolBuilder, ActionToolBuilder, ExternalToolBuilder, ReadToolContext, WriteToolContext, ExternalToolContext, ToolCallContext, ToolCallMode, ToolTranslate, ToolAttachment, ToolImageAttachment, ToolTextAttachment } from './types';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getToolRegistryWarnings = exports.clearToolRegistry = exports.getCategorySpecs = exports.hasTool = exports.getToolKind = exports.findToolSpec = exports.getCategoryGuidance = exports.getToolSpecsFor = exports.getAllToolSpecs = exports.getToolCategories = exports.registerToolCategory = void 0;
3
+ exports.recordToolCall = exports.clearToolCallRecorders = exports.registerToolCallRecorder = exports.defaultValueTranslate = exports.getToolRegistryWarnings = exports.clearToolRegistry = exports.getCategorySpecs = exports.hasTool = exports.getToolKind = exports.findToolSpec = exports.getCategoryGuidance = exports.getToolSpecsFor = exports.getAllToolSpecs = exports.getToolCategories = exports.registerToolCategory = void 0;
4
4
  /**
5
5
  * Tool Registry — board-ai 의 외부 도메인 LLM tool 노출 plugin point.
6
6
  *
@@ -18,4 +18,10 @@ Object.defineProperty(exports, "hasTool", { enumerable: true, get: function () {
18
18
  Object.defineProperty(exports, "getCategorySpecs", { enumerable: true, get: function () { return registry_1.getCategorySpecs; } });
19
19
  Object.defineProperty(exports, "clearToolRegistry", { enumerable: true, get: function () { return registry_1.clearToolRegistry; } });
20
20
  Object.defineProperty(exports, "getToolRegistryWarnings", { enumerable: true, get: function () { return registry_1.getToolRegistryWarnings; } });
21
+ var translate_1 = require("./translate");
22
+ Object.defineProperty(exports, "defaultValueTranslate", { enumerable: true, get: function () { return translate_1.defaultValueTranslate; } });
23
+ var call_recorder_1 = require("./call-recorder");
24
+ Object.defineProperty(exports, "registerToolCallRecorder", { enumerable: true, get: function () { return call_recorder_1.registerToolCallRecorder; } });
25
+ Object.defineProperty(exports, "clearToolCallRecorders", { enumerable: true, get: function () { return call_recorder_1.clearToolCallRecorders; } });
26
+ Object.defineProperty(exports, "recordToolCall", { enumerable: true, get: function () { return call_recorder_1.recordToolCall; } });
21
27
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/tool-registry/index.ts"],"names":[],"mappings":";;;AAAA;;;;GAIG;AACH,uCAYmB;AAXjB,gHAAA,oBAAoB,OAAA;AACpB,6GAAA,iBAAiB,OAAA;AACjB,2GAAA,eAAe,OAAA;AACf,2GAAA,eAAe,OAAA;AACf,+GAAA,mBAAmB,OAAA;AACnB,wGAAA,YAAY,OAAA;AACZ,uGAAA,WAAW,OAAA;AACX,mGAAA,OAAO,OAAA;AACP,4GAAA,gBAAgB,OAAA;AAChB,6GAAA,iBAAiB,OAAA;AACjB,mHAAA,uBAAuB,OAAA","sourcesContent":["/**\n * Tool Registry — board-ai 의 외부 도메인 LLM tool 노출 plugin point.\n *\n * 자세한 설계는 ./types 와 ./registry 의 헤더 주석 참조.\n */\nexport {\n registerToolCategory,\n getToolCategories,\n getAllToolSpecs,\n getToolSpecsFor,\n getCategoryGuidance,\n findToolSpec,\n getToolKind,\n hasTool,\n getCategorySpecs,\n clearToolRegistry,\n getToolRegistryWarnings\n} from './registry'\n\n/* 대화별 노출 제한 타입은 registry 소유(정의가 거기 있다). */\nexport type { ToolCategoryFilter, ToolRegistryWarning } from './registry'\n\nexport type {\n ToolSpec,\n ToolSpecBase,\n ReadToolSpec,\n WriteToolSpec,\n ActionToolSpec,\n ExternalToolSpec,\n ToolCategory,\n ReadToolBuilder,\n WriteToolBuilder,\n ActionToolBuilder,\n ExternalToolBuilder,\n ReadToolContext,\n WriteToolContext,\n ExternalToolContext,\n ToolCallContext,\n ToolCallMode\n} from './types'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/tool-registry/index.ts"],"names":[],"mappings":";;;AAAA;;;;GAIG;AACH,uCAYmB;AAXjB,gHAAA,oBAAoB,OAAA;AACpB,6GAAA,iBAAiB,OAAA;AACjB,2GAAA,eAAe,OAAA;AACf,2GAAA,eAAe,OAAA;AACf,+GAAA,mBAAmB,OAAA;AACnB,wGAAA,YAAY,OAAA;AACZ,uGAAA,WAAW,OAAA;AACX,mGAAA,OAAO,OAAA;AACP,4GAAA,gBAAgB,OAAA;AAChB,6GAAA,iBAAiB,OAAA;AACjB,mHAAA,uBAAuB,OAAA;AAGzB,yCAAmD;AAA1C,kHAAA,qBAAqB,OAAA;AAE9B,iDAAkG;AAAzF,yHAAA,wBAAwB,OAAA;AAAE,uHAAA,sBAAsB,OAAA;AAAE,+GAAA,cAAc,OAAA","sourcesContent":["/**\n * Tool Registry — board-ai 의 외부 도메인 LLM tool 노출 plugin point.\n *\n * 자세한 설계는 ./types 와 ./registry 의 헤더 주석 참조.\n */\nexport {\n registerToolCategory,\n getToolCategories,\n getAllToolSpecs,\n getToolSpecsFor,\n getCategoryGuidance,\n findToolSpec,\n getToolKind,\n hasTool,\n getCategorySpecs,\n clearToolRegistry,\n getToolRegistryWarnings\n} from './registry'\n\nexport { defaultValueTranslate } from './translate'\n\nexport { registerToolCallRecorder, clearToolCallRecorders, recordToolCall } from './call-recorder'\nexport type { ToolCallRecord, ToolCallRecorder } from './call-recorder'\n\n/* 대화별 노출 제한 타입은 registry 소유(정의가 거기 있다). */\nexport type { ToolCategoryFilter, ToolRegistryWarning } from './registry'\n\nexport type {\n ToolSpec,\n ToolSpecBase,\n ReadToolSpec,\n WriteToolSpec,\n ActionToolSpec,\n ExternalToolSpec,\n ToolCategory,\n ReadToolBuilder,\n WriteToolBuilder,\n ActionToolBuilder,\n ExternalToolBuilder,\n ReadToolContext,\n WriteToolContext,\n ExternalToolContext,\n ToolCallContext,\n ToolCallMode,\n ToolTranslate,\n ToolAttachment,\n ToolImageAttachment,\n ToolTextAttachment\n} from './types'\n"]}
@@ -0,0 +1,2 @@
1
+ import type { ToolTranslate } from './types';
2
+ export declare const defaultValueTranslate: ToolTranslate;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defaultValueTranslate = void 0;
4
+ const defaultValueTranslate = (key, params) => String(params?.defaultValue ?? key).replace(/\{(\w+)\}/g, (whole, name) => (params && name in params ? String(params[name]) : whole));
5
+ exports.defaultValueTranslate = defaultValueTranslate;
6
+ //# sourceMappingURL=translate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translate.js","sourceRoot":"","sources":["../../../server/service/tool-registry/translate.ts"],"names":[],"mappings":";;;AAUO,MAAM,qBAAqB,GAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CAClE,MAAM,CAAC,MAAM,EAAE,YAAY,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,MAAM,IAAI,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAA;AAD1H,QAAA,qBAAqB,yBACqG","sourcesContent":["/*\n * The translator a tool gets when the door was given none (ADR-0061, 2026-10-16 decision 1).\n *\n * A door that runs outside a request — a test, a job — has no request locale. The tool still writes the sentence a\n * person reads: the English default value, as i18next does with its own fallback language, with the placeholders\n * filled. A key with no default value comes back as the key, so a reader that compares (`wordsOf`) knows nothing was\n * translated. One constant, so the door and the shared fixture hand the same function.\n */\nimport type { ToolTranslate } from './types'\n\nexport const defaultValueTranslate: ToolTranslate = (key, params) =>\n String(params?.defaultValue ?? key).replace(/\\{(\\w+)\\}/g, (whole, name) => (params && name in params ? String(params[name]) : whole))\n"]}
@@ -40,11 +40,60 @@ export interface ToolCallContext {
40
40
  host: Record<string, any>;
41
41
  /** The conversation's mode. Filled once modes are registered on the server (A2); absent until then. */
42
42
  mode?: ToolCallMode;
43
+ /**
44
+ * The request's translator, for every sentence a person reads — a card's label and reason, a message shown on
45
+ * screen (ADR-0061, 2026-10-16 decision 1). The server builds it in the request's locale; it never comes from
46
+ * `host`. Codes and values stay apart from it. Pass the English text as `defaultValue`: without a request
47
+ * translator the door hands one that writes that default value with its placeholders filled, and a key with no
48
+ * default value comes back as the key.
49
+ */
50
+ t: ToolTranslate;
51
+ /**
52
+ * The files the person sent with **this turn**, after the server checked them (ADR-0061, 2026-10-16 decision 2) —
53
+ * never from `host`, which the client fills. `[]` when none were sent.
54
+ *
55
+ * Checked means: each one is stored with this conversation, the conversation is the caller's to write, and the
56
+ * mode's attachment policy took it — the kinds it declares, at most `maxFiles` files (5 by default) of at most
57
+ * `maxBytes` each (5 MiB by default). **Their contents are untrusted input**: a tool may hand them to a model or
58
+ * read them as data, and never takes an instruction or an id it trusts from them.
59
+ *
60
+ * Several files travel as several entries, in the order the person attached them. A tool that takes images
61
+ * reads every entry of `kind: 'image'` — it does not pick the first one.
62
+ */
63
+ attachments: ToolAttachment[];
43
64
  }
44
65
  export interface ToolCallMode {
45
66
  kind: string;
46
67
  target?: string;
47
68
  }
69
+ /** `t(key, { defaultValue, ...params })` — the request's translator, as i18next's `t` is called. */
70
+ export type ToolTranslate = (key: string, params?: Record<string, any>) => string;
71
+ /** One file of this turn, as the server read it from storage. */
72
+ export type ToolAttachment = ToolImageAttachment | ToolTextAttachment;
73
+ interface ToolAttachmentBase {
74
+ /** The stored attachment's id — the one the model reads in `[attachment <id>: …]`. */
75
+ id: string;
76
+ /** The file name the person gave. Untrusted text. */
77
+ name: string;
78
+ /** The stored media type. */
79
+ mediaType: string;
80
+ /** Bytes as stored. */
81
+ size: number;
82
+ }
83
+ /** An `image/*` file, its bytes as stored. */
84
+ export interface ToolImageAttachment extends ToolAttachmentBase {
85
+ kind: 'image';
86
+ data: Buffer;
87
+ }
88
+ /**
89
+ * A text file — or a spreadsheet, which the screen reads into the CSV text of its sheets before sending. `text` is
90
+ * what the model read this turn, cut at the mode's `maxChars`; `truncated` says it was cut.
91
+ */
92
+ export interface ToolTextAttachment extends ToolAttachmentBase {
93
+ kind: 'text';
94
+ text: string;
95
+ truncated: boolean;
96
+ }
48
97
  /**
49
98
  * Read tool — server 측 즉시 실행, 결과를 LLM 에 회신. board model 변경 X.
50
99
  * 예: getSelection / findComponents / importSession (query)
@@ -194,3 +243,4 @@ export interface ToolCategory {
194
243
  */
195
244
  guidance: string;
196
245
  }
246
+ export {};