hanzoai 8.5.621 → 8.5.640

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 (168) hide show
  1. package/README.md +64 -9
  2. package/dist/answer.js +20 -10
  3. package/dist/api/ai-api.d.ts +280 -14
  4. package/dist/api/ai-api.js +315 -29
  5. package/dist/api/auto-api.d.ts +64 -0
  6. package/dist/api/auto-api.js +77 -0
  7. package/dist/api/billing-api.d.ts +4 -4
  8. package/dist/api/billing-api.js +4 -4
  9. package/dist/api/git-api.d.ts +30 -0
  10. package/dist/api/git-api.js +59 -0
  11. package/dist/api/task-api.d.ts +55 -0
  12. package/dist/api/task-api.js +71 -0
  13. package/dist/client.d.ts +3 -2
  14. package/dist/client.js +4 -3
  15. package/dist/esm/answer.js +20 -10
  16. package/dist/esm/api/ai-api.d.ts +280 -14
  17. package/dist/esm/api/ai-api.js +315 -29
  18. package/dist/esm/api/auto-api.d.ts +64 -0
  19. package/dist/esm/api/auto-api.js +77 -0
  20. package/dist/esm/api/billing-api.d.ts +4 -4
  21. package/dist/esm/api/billing-api.js +4 -4
  22. package/dist/esm/api/git-api.d.ts +30 -0
  23. package/dist/esm/api/git-api.js +59 -0
  24. package/dist/esm/api/task-api.d.ts +55 -0
  25. package/dist/esm/api/task-api.js +71 -0
  26. package/dist/esm/client.d.ts +3 -2
  27. package/dist/esm/client.js +3 -2
  28. package/dist/esm/hanzo.d.ts +3 -0
  29. package/dist/esm/hanzo.js +6 -0
  30. package/dist/esm/models/agent-target-view.d.ts +6 -0
  31. package/dist/esm/models/ai-chat-in.d.ts +42 -0
  32. package/dist/esm/models/ai-chat.d.ts +37 -0
  33. package/dist/esm/models/ai-decide-in.d.ts +50 -0
  34. package/dist/esm/models/ai-decide-in.js +14 -0
  35. package/dist/esm/models/ai-decision-sides.d.ts +4 -5
  36. package/dist/esm/models/{ai-decision-sides-false.d.ts → ai-decision.d.ts} +15 -2
  37. package/dist/esm/models/ai-decision.js +14 -0
  38. package/dist/esm/models/ai-decisions-answer.d.ts +2 -3
  39. package/dist/esm/models/ai-decisions-choice.d.ts +4 -8
  40. package/dist/esm/models/ai-decisions-noul.d.ts +2 -3
  41. package/dist/esm/models/ai-decisions-request.d.ts +2 -3
  42. package/dist/esm/models/ai-decisions-score.d.ts +4 -5
  43. package/dist/esm/models/ai-model.d.ts +73 -0
  44. package/dist/esm/models/ai-model.js +14 -0
  45. package/dist/esm/models/{ai-decisions-choice-criteria-value.d.ts → ai-models.d.ts} +15 -2
  46. package/dist/esm/models/ai-models.js +14 -0
  47. package/dist/esm/models/ai-price.d.ts +48 -0
  48. package/dist/esm/models/ai-price.js +14 -0
  49. package/dist/esm/models/ai-receipt.d.ts +60 -0
  50. package/dist/esm/models/ai-receipt.js +14 -0
  51. package/dist/esm/models/ai-routing-reward-request.d.ts +54 -0
  52. package/dist/esm/models/ai-routing-reward-request.js +24 -0
  53. package/dist/esm/models/auto-automation-in.d.ts +6 -0
  54. package/dist/esm/models/auto-automation-patch.d.ts +6 -0
  55. package/dist/esm/models/auto-automation-run.d.ts +14 -1
  56. package/dist/esm/models/auto-automation.d.ts +6 -0
  57. package/dist/esm/models/auto-post-result.d.ts +36 -0
  58. package/dist/esm/models/auto-post-result.js +14 -0
  59. package/dist/esm/models/auto-review-in.d.ts +36 -0
  60. package/dist/esm/models/auto-review-in.js +14 -0
  61. package/dist/esm/models/git-workspace-view.d.ts +36 -0
  62. package/dist/esm/models/git-workspace-view.js +14 -0
  63. package/dist/esm/models/iam-user.d.ts +1 -1
  64. package/dist/esm/models/index.d.ts +14 -2
  65. package/dist/esm/models/index.js +14 -2
  66. package/dist/esm/models/provider-github-import-out.d.ts +6 -0
  67. package/dist/esm/models/provider-github-repo-item.d.ts +6 -0
  68. package/dist/esm/models/task-comment-list.d.ts +31 -0
  69. package/dist/esm/models/task-comment-list.js +14 -0
  70. package/dist/esm/models/task-comment.d.ts +42 -0
  71. package/dist/esm/models/task-comment.js +14 -0
  72. package/dist/esm/transport.d.ts +4 -0
  73. package/dist/esm/transport.js +188 -0
  74. package/dist/esm/usage.d.ts +98 -0
  75. package/dist/esm/usage.js +161 -0
  76. package/dist/hanzo.d.ts +3 -0
  77. package/dist/hanzo.js +16 -1
  78. package/dist/models/agent-target-view.d.ts +6 -0
  79. package/dist/models/ai-chat-in.d.ts +42 -0
  80. package/dist/models/ai-chat.d.ts +37 -0
  81. package/dist/models/ai-decide-in.d.ts +50 -0
  82. package/dist/models/ai-decide-in.js +15 -0
  83. package/dist/models/ai-decision-sides.d.ts +4 -5
  84. package/dist/models/{ai-decision-sides-false.d.ts → ai-decision.d.ts} +15 -2
  85. package/dist/models/ai-decision.js +15 -0
  86. package/dist/models/ai-decisions-answer.d.ts +2 -3
  87. package/dist/models/ai-decisions-choice.d.ts +4 -8
  88. package/dist/models/ai-decisions-noul.d.ts +2 -3
  89. package/dist/models/ai-decisions-request.d.ts +2 -3
  90. package/dist/models/ai-decisions-score.d.ts +4 -5
  91. package/dist/models/ai-model.d.ts +73 -0
  92. package/dist/models/ai-model.js +15 -0
  93. package/dist/models/{ai-decisions-choice-criteria-value.d.ts → ai-models.d.ts} +15 -2
  94. package/dist/models/ai-models.js +15 -0
  95. package/dist/models/ai-price.d.ts +48 -0
  96. package/dist/models/ai-price.js +15 -0
  97. package/dist/models/ai-receipt.d.ts +60 -0
  98. package/dist/models/ai-receipt.js +15 -0
  99. package/dist/models/ai-routing-reward-request.d.ts +54 -0
  100. package/dist/models/ai-routing-reward-request.js +27 -0
  101. package/dist/models/auto-automation-in.d.ts +6 -0
  102. package/dist/models/auto-automation-patch.d.ts +6 -0
  103. package/dist/models/auto-automation-run.d.ts +14 -1
  104. package/dist/models/auto-automation.d.ts +6 -0
  105. package/dist/models/auto-post-result.d.ts +36 -0
  106. package/dist/models/auto-post-result.js +15 -0
  107. package/dist/models/auto-review-in.d.ts +36 -0
  108. package/dist/models/auto-review-in.js +15 -0
  109. package/dist/models/git-workspace-view.d.ts +36 -0
  110. package/dist/models/git-workspace-view.js +15 -0
  111. package/dist/models/iam-user.d.ts +1 -1
  112. package/dist/models/index.d.ts +14 -2
  113. package/dist/models/index.js +14 -2
  114. package/dist/models/provider-github-import-out.d.ts +6 -0
  115. package/dist/models/provider-github-repo-item.d.ts +6 -0
  116. package/dist/models/task-comment-list.d.ts +31 -0
  117. package/dist/models/task-comment-list.js +15 -0
  118. package/dist/models/task-comment.d.ts +42 -0
  119. package/dist/models/task-comment.js +15 -0
  120. package/dist/transport.d.ts +4 -0
  121. package/dist/transport.js +195 -0
  122. package/dist/usage.d.ts +98 -0
  123. package/dist/usage.js +174 -0
  124. package/package.json +1 -1
  125. package/src/answer.ts +23 -10
  126. package/src/api/ai-api.ts +503 -29
  127. package/src/api/auto-api.ts +117 -0
  128. package/src/api/billing-api.ts +4 -4
  129. package/src/api/git-api.ts +68 -0
  130. package/src/api/task-api.ts +101 -0
  131. package/src/client.ts +3 -2
  132. package/src/hanzo.ts +18 -0
  133. package/src/models/agent-target-view.ts +6 -0
  134. package/src/models/ai-chat-in.ts +48 -0
  135. package/src/models/ai-chat.ts +45 -0
  136. package/src/models/ai-decide-in.ts +54 -0
  137. package/src/models/ai-decision-sides.ts +4 -7
  138. package/src/models/{ai-decision-sides-false.ts → ai-decision.ts} +17 -2
  139. package/src/models/ai-decisions-answer.ts +2 -5
  140. package/src/models/ai-decisions-choice.ts +4 -10
  141. package/src/models/ai-decisions-noul.ts +2 -5
  142. package/src/models/ai-decisions-question.ts +0 -3
  143. package/src/models/ai-decisions-request.ts +2 -5
  144. package/src/models/ai-decisions-score.ts +4 -7
  145. package/src/models/ai-model.ts +81 -0
  146. package/src/models/{ai-decisions-choice-criteria-value.ts → ai-models.ts} +17 -2
  147. package/src/models/ai-price.ts +54 -0
  148. package/src/models/ai-receipt.ts +66 -0
  149. package/src/models/ai-routing-reward-request.ts +63 -0
  150. package/src/models/auto-automation-in.ts +6 -0
  151. package/src/models/auto-automation-patch.ts +6 -0
  152. package/src/models/auto-automation-run.ts +16 -1
  153. package/src/models/auto-automation.ts +6 -0
  154. package/src/models/auto-post-result.ts +42 -0
  155. package/src/models/auto-review-in.ts +42 -0
  156. package/src/models/git-workspace-view.ts +42 -0
  157. package/src/models/iam-user.ts +1 -1
  158. package/src/models/index.ts +14 -2
  159. package/src/models/provider-github-import-out.ts +6 -0
  160. package/src/models/provider-github-repo-item.ts +6 -0
  161. package/src/models/task-comment-list.ts +39 -0
  162. package/src/models/task-comment.ts +48 -0
  163. package/src/transport.ts +190 -0
  164. package/src/usage.ts +203 -0
  165. /package/dist/esm/models/{ai-decision-sides-false.js → ai-chat-in.js} +0 -0
  166. /package/dist/esm/models/{ai-decisions-choice-criteria-value.js → ai-chat.js} +0 -0
  167. /package/dist/models/{ai-decision-sides-false.js → ai-chat-in.js} +0 -0
  168. /package/dist/models/{ai-decisions-choice-criteria-value.js → ai-chat.js} +0 -0
@@ -0,0 +1,42 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ /**
4
+ * Hanzo Cloud API
5
+ * The Hanzo Cloud API as a customer calls it: every operation under /v1/ except the operator\'s admin product, relay routes, legacy spellings and capabilities still reached by flag. Tagged by product: the first path segment after /v1/.
6
+ *
7
+ * The version of the OpenAPI document: v1
8
+ *
9
+ *
10
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
11
+ * https://openapi-generator.tech
12
+ * Do not edit the class manually.
13
+ */
14
+
15
+
16
+
17
+ /**
18
+ *
19
+ * @export
20
+ * @interface AutoReviewIn
21
+ */
22
+ export interface AutoReviewIn {
23
+ /**
24
+ * ID is the automation, and Run its run in review, from the path.
25
+ * @type {string}
26
+ * @memberof AutoReviewIn
27
+ */
28
+ 'id'?: string;
29
+ /**
30
+ * Post posts it when true and discards it when false.
31
+ * @type {boolean}
32
+ * @memberof AutoReviewIn
33
+ */
34
+ 'post'?: boolean;
35
+ /**
36
+ *
37
+ * @type {string}
38
+ * @memberof AutoReviewIn
39
+ */
40
+ 'run'?: string;
41
+ }
42
+
@@ -0,0 +1,42 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ /**
4
+ * Hanzo Cloud API
5
+ * The Hanzo Cloud API as a customer calls it: every operation under /v1/ except the operator\'s admin product, relay routes, legacy spellings and capabilities still reached by flag. Tagged by product: the first path segment after /v1/.
6
+ *
7
+ * The version of the OpenAPI document: v1
8
+ *
9
+ *
10
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
11
+ * https://openapi-generator.tech
12
+ * Do not edit the class manually.
13
+ */
14
+
15
+
16
+
17
+ /**
18
+ *
19
+ * @export
20
+ * @interface GitWorkspaceView
21
+ */
22
+ export interface GitWorkspaceView {
23
+ /**
24
+ * Login is the caller\'s forge account in a made workspace.
25
+ * @type {string}
26
+ * @memberof GitWorkspaceView
27
+ */
28
+ 'login'?: string;
29
+ /**
30
+ * Made says the platform made it for the org; false is the estate\'s.
31
+ * @type {boolean}
32
+ * @memberof GitWorkspaceView
33
+ */
34
+ 'made'?: boolean;
35
+ /**
36
+ * Owner is the forge namespace.
37
+ * @type {string}
38
+ * @memberof GitWorkspaceView
39
+ */
40
+ 'owner'?: string;
41
+ }
42
+
@@ -777,7 +777,7 @@ export interface IamUser {
777
777
  */
778
778
  'name'?: string;
779
779
  /**
780
- *
780
+ * NameKey is Fold(Name), derived on every save (BeforeCreate, BeforeUpdate) and on every open for a row saved without it (store.Prepare): the indexed key a case-insensitive username lookup reads (store.GetUserByName). It is always written, empty or not, so a row that has been keyed is never mistaken for one that has not.
781
781
  * @type {string}
782
782
  * @memberof IamUser
783
783
  */
@@ -105,14 +105,16 @@ export * from './ai-anthropic-response';
105
105
  export * from './ai-anthropic-tool';
106
106
  export * from './ai-anthropic-usage';
107
107
  export * from './ai-cache-writes';
108
+ export * from './ai-chat';
109
+ export * from './ai-chat-in';
108
110
  export * from './ai-class';
109
111
  export * from './ai-cost-stats';
112
+ export * from './ai-decide-in';
113
+ export * from './ai-decision';
110
114
  export * from './ai-decision-sides';
111
- export * from './ai-decision-sides-false';
112
115
  export * from './ai-decisions-action';
113
116
  export * from './ai-decisions-answer';
114
117
  export * from './ai-decisions-choice';
115
- export * from './ai-decisions-choice-criteria-value';
116
118
  export * from './ai-decisions-noul';
117
119
  export * from './ai-decisions-question';
118
120
  export * from './ai-decisions-reason';
@@ -134,13 +136,16 @@ export * from './ai-limits';
134
136
  export * from './ai-limits-set';
135
137
  export * from './ai-mcpapp';
136
138
  export * from './ai-mcpsurface';
139
+ export * from './ai-model';
137
140
  export * from './ai-model-access-info';
138
141
  export * from './ai-model-info';
139
142
  export * from './ai-model-list';
140
143
  export * from './ai-model-pricing-info';
141
144
  export * from './ai-model-providers';
145
+ export * from './ai-models';
142
146
  export * from './ai-panel-judge';
143
147
  export * from './ai-paused';
148
+ export * from './ai-price';
144
149
  export * from './ai-provider-usage';
145
150
  export * from './ai-provider-usage-model-spend';
146
151
  export * from './ai-provider-usage-series-point';
@@ -150,6 +155,7 @@ export * from './ai-rank-usage';
150
155
  export * from './ai-ranked-doc';
151
156
  export * from './ai-ranked-text';
152
157
  export * from './ai-ranking';
158
+ export * from './ai-receipt';
153
159
  export * from './ai-response';
154
160
  export * from './ai-responses-input-details';
155
161
  export * from './ai-responses-output-details';
@@ -162,6 +168,7 @@ export * from './ai-router-stats';
162
168
  export * from './ai-routing-edit';
163
169
  export * from './ai-routing-probe';
164
170
  export * from './ai-routing-proposal';
171
+ export * from './ai-routing-reward-request';
165
172
  export * from './ai-routing-reward-result';
166
173
  export * from './ai-routing-view';
167
174
  export * from './ai-stats-window';
@@ -217,7 +224,9 @@ export * from './auto-flow-version';
217
224
  export * from './auto-last-run';
218
225
  export * from './auto-patch-flow-in';
219
226
  export * from './auto-populated-flow';
227
+ export * from './auto-post-result';
220
228
  export * from './auto-prop-spec';
229
+ export * from './auto-review-in';
221
230
  export * from './auto-run-handle';
222
231
  export * from './auto-run-in';
223
232
  export * from './auto-run-page';
@@ -919,6 +928,7 @@ export * from './git-workflow-list';
919
928
  export * from './git-workflow-run';
920
929
  export * from './git-workflow-runs';
921
930
  export * from './git-workflow-view';
931
+ export * from './git-workspace-view';
922
932
  export * from './grants';
923
933
  export * from './graph';
924
934
  export * from './graph-graph-answer-in';
@@ -2684,6 +2694,8 @@ export * from './sync-sync-view';
2684
2694
  export * from './tag-config';
2685
2695
  export * from './task';
2686
2696
  export * from './task-board-view';
2697
+ export * from './task-comment';
2698
+ export * from './task-comment-list';
2687
2699
  export * from './task-issue-edit';
2688
2700
  export * from './task-issue-hit';
2689
2701
  export * from './task-issue-hits';
@@ -20,6 +20,12 @@
20
20
  * @interface ProviderGithubImportOut
21
21
  */
22
22
  export interface ProviderGithubImportOut {
23
+ /**
24
+ * Codebases are the names they take in the org\'s code workspace, in the same order: poll GET /v1/git/repos for each to appear.
25
+ * @type {Array<string>}
26
+ * @memberof ProviderGithubImportOut
27
+ */
28
+ 'codebases'?: Array<string>;
23
29
  /**
24
30
  * Queued is how many repositories were handed to the background importer.
25
31
  * @type {number}
@@ -20,6 +20,12 @@
20
20
  * @interface ProviderGithubRepoItem
21
21
  */
22
22
  export interface ProviderGithubRepoItem {
23
+ /**
24
+ * Codebase is the name it has in the org\'s code workspace once brought in (POST /v1/provider/github/repos/import): listed by GET /v1/git/repos when it is there.
25
+ * @type {string}
26
+ * @memberof ProviderGithubRepoItem
27
+ */
28
+ 'codebase'?: string;
23
29
  /**
24
30
  * DefaultBranch is the branch GitHub opens it on.
25
31
  * @type {string}
@@ -0,0 +1,39 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ /**
4
+ * Hanzo Cloud API
5
+ * The Hanzo Cloud API as a customer calls it: every operation under /v1/ except the operator\'s admin product, relay routes, legacy spellings and capabilities still reached by flag. Tagged by product: the first path segment after /v1/.
6
+ *
7
+ * The version of the OpenAPI document: v1
8
+ *
9
+ *
10
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
11
+ * https://openapi-generator.tech
12
+ * Do not edit the class manually.
13
+ */
14
+
15
+
16
+ // May contain unused imports in some cases
17
+ // @ts-ignore
18
+ import type { TaskComment } from './task-comment';
19
+
20
+ /**
21
+ *
22
+ * @export
23
+ * @interface TaskCommentList
24
+ */
25
+ export interface TaskCommentList {
26
+ /**
27
+ * Data is the conversation. Empty is an empty array, never null.
28
+ * @type {Array<TaskComment>}
29
+ * @memberof TaskCommentList
30
+ */
31
+ 'data'?: Array<TaskComment>;
32
+ /**
33
+ * Home is the GitHub repository (owner/name) the issue lives on when its board mirrors one; its conversation is read there, and Data is then empty.
34
+ * @type {string}
35
+ * @memberof TaskCommentList
36
+ */
37
+ 'home'?: string;
38
+ }
39
+
@@ -0,0 +1,48 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ /**
4
+ * Hanzo Cloud API
5
+ * The Hanzo Cloud API as a customer calls it: every operation under /v1/ except the operator\'s admin product, relay routes, legacy spellings and capabilities still reached by flag. Tagged by product: the first path segment after /v1/.
6
+ *
7
+ * The version of the OpenAPI document: v1
8
+ *
9
+ *
10
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
11
+ * https://openapi-generator.tech
12
+ * Do not edit the class manually.
13
+ */
14
+
15
+
16
+
17
+ /**
18
+ *
19
+ * @export
20
+ * @interface TaskComment
21
+ */
22
+ export interface TaskComment {
23
+ /**
24
+ * Author is who wrote it: their name, else their forge login.
25
+ * @type {string}
26
+ * @memberof TaskComment
27
+ */
28
+ 'author'?: string;
29
+ /**
30
+ * Body is what was written, as markdown.
31
+ * @type {string}
32
+ * @memberof TaskComment
33
+ */
34
+ 'body'?: string;
35
+ /**
36
+ * CreatedAt is when it was written, RFC 3339.
37
+ * @type {string}
38
+ * @memberof TaskComment
39
+ */
40
+ 'createdAt'?: string;
41
+ /**
42
+ * ID is the forge\'s id for the comment.
43
+ * @type {number}
44
+ * @memberof TaskComment
45
+ */
46
+ 'id'?: number;
47
+ }
48
+
@@ -0,0 +1,190 @@
1
+ // transport — the Configuration this package exports.
2
+ //
3
+ // It is the generated Configuration plus one thing: a request a generated *Api
4
+ // class sends with it that comes back 402 or 429 with a usage refusal in the
5
+ // body throws that refusal's typed error (usage.ts). Every other answer passes
6
+ // through untouched — a 400 is still the AxiosError it was.
7
+ //
8
+ // The error path is axios's own: one response interceptor on the axios instance
9
+ // the generated classes use by default, installed by the first Configuration
10
+ // built. It acts only on requests marked as this package's, and the mark rides
11
+ // in `baseOptions`, which the generated client spreads into every request it
12
+ // builds — so the caller's other axios traffic, adapters and transforms are left
13
+ // as they were. An axios instance passed as an *Api's third argument has
14
+ // interceptors of its own, and a refusal through it stays an AxiosError.
15
+ //
16
+ // hanzo.ts exports this class under the generated one's name, so
17
+ // `new Configuration({ accessToken })` from 'hanzoai' is this one, and so is
18
+ // `Client.configuration`.
19
+
20
+ import axios, { type AxiosError, type AxiosResponse } from 'axios';
21
+ import { Configuration as Generated, type ConfigurationParameters } from './configuration';
22
+ import { header, limited } from './usage';
23
+
24
+ /**
25
+ * The request option that marks a request as sent with this Configuration. Its
26
+ * value is this module's own interceptor, so two copies of the package sharing
27
+ * one axios each type only their own requests, with their own classes.
28
+ */
29
+ const MARK = 'hanzoUsage';
30
+
31
+ /** A stream body is read this far at most; a refusal is a few hundred bytes of JSON. */
32
+ const CAP = 1 << 16;
33
+
34
+ type Manager = { handlers?: Array<{ rejected?: unknown } | null> };
35
+
36
+ export class Configuration extends Generated {
37
+ constructor(param: ConfigurationParameters = {}) {
38
+ super(param);
39
+ this.baseOptions = { ...this.baseOptions, [MARK]: refused };
40
+ // Checked, not remembered: a test teardown's `interceptors.response.clear()`
41
+ // removes it, and the next Configuration puts it back.
42
+ const installed = (axios.interceptors.response as unknown as Manager).handlers?.some((h) => h?.rejected === refused);
43
+ if (!installed) axios.interceptors.response.use(undefined, refused);
44
+ }
45
+ }
46
+
47
+ /** Rethrow a marked 402 or 429 as the usage refusal its body names; anything else as it came. */
48
+ async function refused(err: unknown): Promise<never> {
49
+ const e = err as AxiosError | undefined;
50
+ const res = e?.response;
51
+ const config = e?.config as (Record<string, unknown> & { signal?: unknown; timeout?: number }) | undefined;
52
+ if (!res || config?.[MARK] !== refused || (res.status !== 402 && res.status !== 429)) throw err;
53
+ let parsed: unknown;
54
+ try {
55
+ parsed = await body(res, config);
56
+ } catch {
57
+ // A body that cannot be read names nothing: the answer is the one axios gave.
58
+ throw err;
59
+ }
60
+ throw limited(res.status, parsed, header(res.headers, 'x-request-id'), err) ?? err;
61
+ }
62
+
63
+ /**
64
+ * The body as JSON where it is JSON. axios hands it over parsed by default and
65
+ * raw when the caller asked for a buffer, a blob or a stream. A stream is read
66
+ * only when it says it is JSON — a refusal is, an event stream never is — at
67
+ * most CAP bytes, no longer than the caller's signal and timeout allow, and
68
+ * without taking it from the caller: `res.data` is left holding a stream of the
69
+ * same family that yields every byte again.
70
+ */
71
+ async function body(res: AxiosResponse, config: { signal?: unknown; timeout?: number }): Promise<unknown> {
72
+ let data: unknown = res.data;
73
+ if (web(data) || node(data)) {
74
+ if (!/json/i.test(header(res.headers, 'content-type') ?? '')) return undefined;
75
+ const read = web(data) ? peekWeb(data, config) : peekNode(data, config);
76
+ const { text, again } = await read;
77
+ res.data = again;
78
+ data = text;
79
+ } else if (typeof Blob !== 'undefined' && data instanceof Blob) {
80
+ data = await data.text();
81
+ } else if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
82
+ data = new TextDecoder().decode(data as Uint8Array);
83
+ }
84
+ if (typeof data !== 'string') return data;
85
+ try {
86
+ return JSON.parse(data) as unknown;
87
+ } catch {
88
+ return data;
89
+ }
90
+ }
91
+
92
+ /** A WHATWG stream: the fetch and xhr adapters' `responseType: 'stream'`. */
93
+ const web = (v: unknown): v is ReadableStream<Uint8Array> =>
94
+ typeof (v as ReadableStream | null)?.tee === 'function' && typeof (v as ReadableStream).getReader === 'function';
95
+
96
+ /** A Node stream: the http adapter's `responseType: 'stream'`. */
97
+ type Stream = AsyncIterable<unknown> & { pipe: unknown; constructor: { from(source: AsyncIterable<unknown>): unknown } };
98
+ const node = (v: unknown): v is Stream =>
99
+ typeof (v as Stream | null)?.pipe === 'function' &&
100
+ typeof (v as Stream)[Symbol.asyncIterator] === 'function' &&
101
+ typeof (v as Stream).constructor?.from === 'function';
102
+
103
+ /** Read a WHATWG stream through `peek`, and hand back a WHATWG stream that replays it. */
104
+ async function peekWeb(stream: ReadableStream<Uint8Array>, config: { signal?: unknown; timeout?: number }) {
105
+ const reader = stream.getReader();
106
+ const { text, rest } = await peek(() => reader.read(), config);
107
+ const again = new ReadableStream<Uint8Array>({
108
+ async pull(controller) {
109
+ const step = await rest.next();
110
+ if (step.done) controller.close();
111
+ else controller.enqueue(step.value as Uint8Array);
112
+ },
113
+ cancel: (reason) => reader.cancel(reason),
114
+ });
115
+ return { text, again };
116
+ }
117
+
118
+ /** Read a Node stream through `peek`; `Readable.from` comes off its own constructor, so no bundle gains node:stream. */
119
+ async function peekNode(stream: Stream, config: { signal?: unknown; timeout?: number }) {
120
+ const it = stream[Symbol.asyncIterator]();
121
+ const { text, rest } = await peek(() => it.next(), config);
122
+ return { text, again: stream.constructor.from(rest) };
123
+ }
124
+
125
+ const STOP = Symbol('stop');
126
+
127
+ /**
128
+ * Read chunks until the end, CAP bytes, the caller's abort or its timeout,
129
+ * whichever comes first. `text` is the whole body only when the end was
130
+ * reached — anything less names no refusal. `rest` yields every chunk read and
131
+ * then whatever is left, including a read still in flight and a failure, so the
132
+ * caller's stream loses nothing.
133
+ */
134
+ async function peek(next: () => Promise<IteratorResult<unknown>>, config: { signal?: unknown; timeout?: number }) {
135
+ const seen: unknown[] = [];
136
+ const decoder = new TextDecoder();
137
+ let text = '';
138
+ let done = false;
139
+ let failed: { error: unknown } | undefined;
140
+ let pending: Promise<IteratorResult<unknown>> | undefined;
141
+
142
+ let timer: ReturnType<typeof setTimeout> | undefined;
143
+ let quit: (() => void) | undefined;
144
+ const stop = new Promise<typeof STOP>((resolve) => {
145
+ quit = () => resolve(STOP);
146
+ const signal = config.signal as AbortSignal | undefined;
147
+ if (signal && typeof signal.addEventListener === 'function') {
148
+ if (signal.aborted) resolve(STOP);
149
+ signal.addEventListener('abort', () => resolve(STOP), { once: true });
150
+ }
151
+ if (config.timeout && config.timeout > 0) timer = setTimeout(() => resolve(STOP), config.timeout);
152
+ });
153
+
154
+ try {
155
+ while (text.length < CAP) {
156
+ pending = next();
157
+ const step = await Promise.race([pending, stop]);
158
+ if (step === STOP) break;
159
+ pending = undefined;
160
+ if (step.done) {
161
+ done = true;
162
+ break;
163
+ }
164
+ seen.push(step.value);
165
+ text += typeof step.value === 'string' ? step.value : decoder.decode(step.value as Uint8Array, { stream: true });
166
+ }
167
+ } catch (error) {
168
+ pending = undefined;
169
+ failed = { error };
170
+ } finally {
171
+ if (timer) clearTimeout(timer);
172
+ quit?.();
173
+ }
174
+
175
+ async function* rest() {
176
+ yield* seen;
177
+ if (failed) throw failed.error;
178
+ if (pending) {
179
+ const step = await pending;
180
+ if (step.done) return;
181
+ yield step.value;
182
+ }
183
+ while (!done) {
184
+ const step = await next();
185
+ if (step.done) return;
186
+ yield step.value;
187
+ }
188
+ }
189
+ return { text: done ? text : undefined, rest: rest() };
190
+ }
package/src/usage.ts ADDED
@@ -0,0 +1,203 @@
1
+ // usage — how a priced request was paid for, and the refusals when it cannot be.
2
+ //
3
+ // Every AI answer says where the caller's plan stands in six headers, never a
4
+ // figure: X-Hanzo-Usage (ok | near | limited), -Usage-Class (premium | ours |
5
+ // free), -Paid-By (plan | credits | free), -Fallback (the model that answered
6
+ // instead), -Served (the Hanzo SKU that answered) and -Usage-Reason (the refusal
7
+ // code that caused a fallback). `readUsage` reads them off whatever headers the
8
+ // caller holds.
9
+ //
10
+ // A request nothing may pay for is refused in the OpenAI envelope,
11
+ //
12
+ // {"error": {"message", "type", "code", "class"?, "model"?, "fallback"?,
13
+ // "resets_at"?, "upgrade_url"?, "actions"?}}
14
+ //
15
+ // and six codes in it are six different facts with six different ways out, so
16
+ // each is a class: a caller tells them apart with `instanceof`, never by parsing
17
+ // the sentence. The names are the Python and Go SDKs' names. Both transports
18
+ // throw them from the error path they already had — an axios response
19
+ // interceptor for the generated client (transport.ts) and the six capabilities'
20
+ // Fault (answer.ts) — through `limited`, the one place a body becomes one.
21
+
22
+ import { list, obj, type Obj } from './read';
23
+
24
+ /** One way out of a usage refusal, in the order to offer them. */
25
+ export interface UsageAction {
26
+ /** upgrade | switch | credits | topup. */
27
+ kind: string;
28
+ /** The words to put on the button. */
29
+ label?: string;
30
+ /** Where to go: the pay page, or `/v1/ai/limits` for "continue with credits". */
31
+ url?: string;
32
+ /** The plan an `upgrade` moves to. */
33
+ plan?: string;
34
+ /** The model a `switch` moves to. */
35
+ model?: string;
36
+ }
37
+
38
+ /** A string the server sent, or undefined where it sent none — never "". */
39
+ const text = (v: unknown): string | undefined => (typeof v === 'string' && v !== '' ? v : undefined);
40
+
41
+ /** An RFC 3339 instant, or undefined where none was sent or it does not parse. */
42
+ const instant = (v: unknown): Date | undefined => {
43
+ const s = text(v);
44
+ if (!s) return undefined;
45
+ const d = new Date(s);
46
+ return Number.isNaN(d.getTime()) ? undefined : d;
47
+ };
48
+
49
+ /** A priced request refused because nothing the caller holds may pay for it. */
50
+ export class UsageLimitError extends Error {
51
+ // Literal, not `new.target.name`, which a minifier renames.
52
+ override name = 'UsageLimitError';
53
+ /** 402 or 429. */
54
+ readonly status: number;
55
+ /** The machine code — one per subclass. */
56
+ readonly code: string;
57
+ /** `billing_error` or `rate_limit_error`. */
58
+ readonly type: string | undefined;
59
+ /** The class of usage refused: premium, ours or free. */
60
+ readonly usageClass: string | undefined;
61
+ /** The model the refusal is about. */
62
+ readonly model: string | undefined;
63
+ /** The model that answers instead, where there is one. */
64
+ readonly fallback: string | undefined;
65
+ /** When the refusal lifts by itself. */
66
+ readonly resetsAt: Date | undefined;
67
+ /** The upgrade page. */
68
+ readonly upgradeUrl: string | undefined;
69
+ readonly actions: UsageAction[];
70
+ /** `x-request-id` — the handle that finds this call in the audit trail. */
71
+ readonly request: string | undefined;
72
+ /** What the transport raised: the AxiosError on the generated client, nothing on the six. */
73
+ readonly cause: unknown;
74
+
75
+ constructor(status: number, error: Obj, request?: string, cause?: unknown) {
76
+ const code = text(error['code']) ?? '';
77
+ super(text(error['message']) ?? `HTTP ${status} ${code}`);
78
+ this.status = status;
79
+ this.code = code;
80
+ this.type = text(error['type']);
81
+ this.usageClass = text(error['class']);
82
+ this.model = text(error['model']);
83
+ this.fallback = text(error['fallback']);
84
+ this.resetsAt = instant(error['resets_at']);
85
+ this.upgradeUrl = text(error['upgrade_url']);
86
+ // An entry with no kind is not an action, so it is not offered.
87
+ this.actions = list(error['actions']).flatMap((a) => {
88
+ const o = obj(a);
89
+ const kind = text(o['kind']);
90
+ if (!kind) return [];
91
+ const out: UsageAction = { kind };
92
+ for (const k of ['label', 'url', 'plan', 'model'] as const) {
93
+ const v = text(o[k]);
94
+ if (v !== undefined) out[k] = v;
95
+ }
96
+ return [out];
97
+ });
98
+ this.request = request;
99
+ this.cause = cause;
100
+ }
101
+ }
102
+
103
+ /** 402 `plan_allowance_used` — the plan's included usage of the class is used, and nothing else may pay. */
104
+ export class PlanAllowanceUsedError extends UsageLimitError {
105
+ override name = 'PlanAllowanceUsedError';
106
+ }
107
+
108
+ /** 402 `paid_plan_required` — the model needs a paid plan or prepaid balance. */
109
+ export class PaidPlanRequiredError extends UsageLimitError {
110
+ override name = 'PaidPlanRequiredError';
111
+ }
112
+
113
+ /** 429 `free_plan_cap` — the free plan's daily cap on the model is used; it lifts at `resetsAt`. */
114
+ export class FreePlanCapError extends UsageLimitError {
115
+ override name = 'FreePlanCapError';
116
+ }
117
+
118
+ /** 402 `model_cap` — the model has used its share of the plan; `fallback` answers instead. */
119
+ export class ModelCapError extends UsageLimitError {
120
+ override name = 'ModelCapError';
121
+ }
122
+
123
+ /** 429 `usage_cap_exceeded` — a session or day request window is spent. */
124
+ export class UsageCapExceededError extends UsageLimitError {
125
+ override name = 'UsageCapExceededError';
126
+ }
127
+
128
+ /** 402 `insufficient_balance` — the known wallet balance cannot cover the request. */
129
+ export class InsufficientBalanceError extends UsageLimitError {
130
+ override name = 'InsufficientBalanceError';
131
+ }
132
+
133
+ // A Map, not an object literal: a code of "constructor" must find nothing.
134
+ const classes = new Map<string, new (status: number, error: Obj, request?: string, cause?: unknown) => UsageLimitError>([
135
+ ['plan_allowance_used', PlanAllowanceUsedError],
136
+ ['paid_plan_required', PaidPlanRequiredError],
137
+ ['free_plan_cap', FreePlanCapError],
138
+ ['model_cap', ModelCapError],
139
+ ['usage_cap_exceeded', UsageCapExceededError],
140
+ ['insufficient_balance', InsufficientBalanceError],
141
+ ]);
142
+
143
+ /**
144
+ * The typed error a refusal names, or undefined: only a 402 or 429 whose
145
+ * `error.code` is one of the six is one. Anything else stays whatever error its
146
+ * transport already raises.
147
+ */
148
+ export function limited(status: number, body: unknown, request?: string, cause?: unknown): UsageLimitError | undefined {
149
+ if (status !== 402 && status !== 429) return undefined;
150
+ const error = obj(obj(body)['error']);
151
+ const Class = classes.get(text(error['code']) ?? '');
152
+ return Class ? new Class(status, error, request, cause) : undefined;
153
+ }
154
+
155
+ /** What the X-Hanzo-* headers on one answer say. Each is undefined where the header is absent. */
156
+ export interface Usage {
157
+ /** X-Hanzo-Usage: ok | near | limited — where the class stands. */
158
+ usage: string | undefined;
159
+ /** X-Hanzo-Usage-Class: premium | ours | free. */
160
+ usageClass: string | undefined;
161
+ /** X-Hanzo-Paid-By: plan | credits | free. Prepaid and granted credit both read `credits`. */
162
+ paidBy: string | undefined;
163
+ /** X-Hanzo-Fallback: the model that answered instead (limited mode). */
164
+ fallback: string | undefined;
165
+ /** X-Hanzo-Served: the Hanzo SKU that answered. */
166
+ served: string | undefined;
167
+ /** X-Hanzo-Usage-Reason: the refusal code that caused a fallback. */
168
+ reason: string | undefined;
169
+ }
170
+
171
+ /** A fetch `Headers`, axios's `AxiosHeaders`, or any record of header name to value. */
172
+ export type HeaderSource = { get(name: string): unknown } | Record<string, unknown>;
173
+
174
+ /**
175
+ * Read the usage headers off one answer: `readUsage(res.headers)` for an axios
176
+ * response or a fetch Response alike. Names match case-insensitively. A header
177
+ * that is absent, or present and empty, reads undefined — never 0 or "", which
178
+ * would claim the server said something it did not.
179
+ */
180
+ export function readUsage(headers: HeaderSource | null | undefined): Usage {
181
+ return {
182
+ usage: header(headers, 'x-hanzo-usage'),
183
+ usageClass: header(headers, 'x-hanzo-usage-class'),
184
+ paidBy: header(headers, 'x-hanzo-paid-by'),
185
+ fallback: header(headers, 'x-hanzo-fallback'),
186
+ served: header(headers, 'x-hanzo-served'),
187
+ reason: header(headers, 'x-hanzo-usage-reason'),
188
+ };
189
+ }
190
+
191
+ /** One header by lower-case name, matched case-insensitively; undefined where absent or empty. */
192
+ export function header(headers: HeaderSource | null | undefined, name: string): string | undefined {
193
+ const h = headers ?? {};
194
+ let v: unknown;
195
+ if (typeof (h as { get?: unknown }).get === 'function') {
196
+ v = (h as { get(name: string): unknown }).get(name);
197
+ } else {
198
+ for (const [k, value] of Object.entries(h)) if (k.toLowerCase() === name) v = value;
199
+ }
200
+ if (Array.isArray(v)) v = v[0];
201
+ if (typeof v === 'number' || typeof v === 'boolean') v = String(v);
202
+ return typeof v === 'string' ? text(v.trim()) : undefined;
203
+ }