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.
- package/README.md +64 -9
- package/dist/answer.js +20 -10
- package/dist/api/ai-api.d.ts +280 -14
- package/dist/api/ai-api.js +315 -29
- package/dist/api/auto-api.d.ts +64 -0
- package/dist/api/auto-api.js +77 -0
- package/dist/api/billing-api.d.ts +4 -4
- package/dist/api/billing-api.js +4 -4
- package/dist/api/git-api.d.ts +30 -0
- package/dist/api/git-api.js +59 -0
- package/dist/api/task-api.d.ts +55 -0
- package/dist/api/task-api.js +71 -0
- package/dist/client.d.ts +3 -2
- package/dist/client.js +4 -3
- package/dist/esm/answer.js +20 -10
- package/dist/esm/api/ai-api.d.ts +280 -14
- package/dist/esm/api/ai-api.js +315 -29
- package/dist/esm/api/auto-api.d.ts +64 -0
- package/dist/esm/api/auto-api.js +77 -0
- package/dist/esm/api/billing-api.d.ts +4 -4
- package/dist/esm/api/billing-api.js +4 -4
- package/dist/esm/api/git-api.d.ts +30 -0
- package/dist/esm/api/git-api.js +59 -0
- package/dist/esm/api/task-api.d.ts +55 -0
- package/dist/esm/api/task-api.js +71 -0
- package/dist/esm/client.d.ts +3 -2
- package/dist/esm/client.js +3 -2
- package/dist/esm/hanzo.d.ts +3 -0
- package/dist/esm/hanzo.js +6 -0
- package/dist/esm/models/agent-target-view.d.ts +6 -0
- package/dist/esm/models/ai-chat-in.d.ts +42 -0
- package/dist/esm/models/ai-chat.d.ts +37 -0
- package/dist/esm/models/ai-decide-in.d.ts +50 -0
- package/dist/esm/models/ai-decide-in.js +14 -0
- package/dist/esm/models/ai-decision-sides.d.ts +4 -5
- package/dist/esm/models/{ai-decision-sides-false.d.ts → ai-decision.d.ts} +15 -2
- package/dist/esm/models/ai-decision.js +14 -0
- package/dist/esm/models/ai-decisions-answer.d.ts +2 -3
- package/dist/esm/models/ai-decisions-choice.d.ts +4 -8
- package/dist/esm/models/ai-decisions-noul.d.ts +2 -3
- package/dist/esm/models/ai-decisions-request.d.ts +2 -3
- package/dist/esm/models/ai-decisions-score.d.ts +4 -5
- package/dist/esm/models/ai-model.d.ts +73 -0
- package/dist/esm/models/ai-model.js +14 -0
- package/dist/esm/models/{ai-decisions-choice-criteria-value.d.ts → ai-models.d.ts} +15 -2
- package/dist/esm/models/ai-models.js +14 -0
- package/dist/esm/models/ai-price.d.ts +48 -0
- package/dist/esm/models/ai-price.js +14 -0
- package/dist/esm/models/ai-receipt.d.ts +60 -0
- package/dist/esm/models/ai-receipt.js +14 -0
- package/dist/esm/models/ai-routing-reward-request.d.ts +54 -0
- package/dist/esm/models/ai-routing-reward-request.js +24 -0
- package/dist/esm/models/auto-automation-in.d.ts +6 -0
- package/dist/esm/models/auto-automation-patch.d.ts +6 -0
- package/dist/esm/models/auto-automation-run.d.ts +14 -1
- package/dist/esm/models/auto-automation.d.ts +6 -0
- package/dist/esm/models/auto-post-result.d.ts +36 -0
- package/dist/esm/models/auto-post-result.js +14 -0
- package/dist/esm/models/auto-review-in.d.ts +36 -0
- package/dist/esm/models/auto-review-in.js +14 -0
- package/dist/esm/models/git-workspace-view.d.ts +36 -0
- package/dist/esm/models/git-workspace-view.js +14 -0
- package/dist/esm/models/iam-user.d.ts +1 -1
- package/dist/esm/models/index.d.ts +14 -2
- package/dist/esm/models/index.js +14 -2
- package/dist/esm/models/provider-github-import-out.d.ts +6 -0
- package/dist/esm/models/provider-github-repo-item.d.ts +6 -0
- package/dist/esm/models/task-comment-list.d.ts +31 -0
- package/dist/esm/models/task-comment-list.js +14 -0
- package/dist/esm/models/task-comment.d.ts +42 -0
- package/dist/esm/models/task-comment.js +14 -0
- package/dist/esm/transport.d.ts +4 -0
- package/dist/esm/transport.js +188 -0
- package/dist/esm/usage.d.ts +98 -0
- package/dist/esm/usage.js +161 -0
- package/dist/hanzo.d.ts +3 -0
- package/dist/hanzo.js +16 -1
- package/dist/models/agent-target-view.d.ts +6 -0
- package/dist/models/ai-chat-in.d.ts +42 -0
- package/dist/models/ai-chat.d.ts +37 -0
- package/dist/models/ai-decide-in.d.ts +50 -0
- package/dist/models/ai-decide-in.js +15 -0
- package/dist/models/ai-decision-sides.d.ts +4 -5
- package/dist/models/{ai-decision-sides-false.d.ts → ai-decision.d.ts} +15 -2
- package/dist/models/ai-decision.js +15 -0
- package/dist/models/ai-decisions-answer.d.ts +2 -3
- package/dist/models/ai-decisions-choice.d.ts +4 -8
- package/dist/models/ai-decisions-noul.d.ts +2 -3
- package/dist/models/ai-decisions-request.d.ts +2 -3
- package/dist/models/ai-decisions-score.d.ts +4 -5
- package/dist/models/ai-model.d.ts +73 -0
- package/dist/models/ai-model.js +15 -0
- package/dist/models/{ai-decisions-choice-criteria-value.d.ts → ai-models.d.ts} +15 -2
- package/dist/models/ai-models.js +15 -0
- package/dist/models/ai-price.d.ts +48 -0
- package/dist/models/ai-price.js +15 -0
- package/dist/models/ai-receipt.d.ts +60 -0
- package/dist/models/ai-receipt.js +15 -0
- package/dist/models/ai-routing-reward-request.d.ts +54 -0
- package/dist/models/ai-routing-reward-request.js +27 -0
- package/dist/models/auto-automation-in.d.ts +6 -0
- package/dist/models/auto-automation-patch.d.ts +6 -0
- package/dist/models/auto-automation-run.d.ts +14 -1
- package/dist/models/auto-automation.d.ts +6 -0
- package/dist/models/auto-post-result.d.ts +36 -0
- package/dist/models/auto-post-result.js +15 -0
- package/dist/models/auto-review-in.d.ts +36 -0
- package/dist/models/auto-review-in.js +15 -0
- package/dist/models/git-workspace-view.d.ts +36 -0
- package/dist/models/git-workspace-view.js +15 -0
- package/dist/models/iam-user.d.ts +1 -1
- package/dist/models/index.d.ts +14 -2
- package/dist/models/index.js +14 -2
- package/dist/models/provider-github-import-out.d.ts +6 -0
- package/dist/models/provider-github-repo-item.d.ts +6 -0
- package/dist/models/task-comment-list.d.ts +31 -0
- package/dist/models/task-comment-list.js +15 -0
- package/dist/models/task-comment.d.ts +42 -0
- package/dist/models/task-comment.js +15 -0
- package/dist/transport.d.ts +4 -0
- package/dist/transport.js +195 -0
- package/dist/usage.d.ts +98 -0
- package/dist/usage.js +174 -0
- package/package.json +1 -1
- package/src/answer.ts +23 -10
- package/src/api/ai-api.ts +503 -29
- package/src/api/auto-api.ts +117 -0
- package/src/api/billing-api.ts +4 -4
- package/src/api/git-api.ts +68 -0
- package/src/api/task-api.ts +101 -0
- package/src/client.ts +3 -2
- package/src/hanzo.ts +18 -0
- package/src/models/agent-target-view.ts +6 -0
- package/src/models/ai-chat-in.ts +48 -0
- package/src/models/ai-chat.ts +45 -0
- package/src/models/ai-decide-in.ts +54 -0
- package/src/models/ai-decision-sides.ts +4 -7
- package/src/models/{ai-decision-sides-false.ts → ai-decision.ts} +17 -2
- package/src/models/ai-decisions-answer.ts +2 -5
- package/src/models/ai-decisions-choice.ts +4 -10
- package/src/models/ai-decisions-noul.ts +2 -5
- package/src/models/ai-decisions-question.ts +0 -3
- package/src/models/ai-decisions-request.ts +2 -5
- package/src/models/ai-decisions-score.ts +4 -7
- package/src/models/ai-model.ts +81 -0
- package/src/models/{ai-decisions-choice-criteria-value.ts → ai-models.ts} +17 -2
- package/src/models/ai-price.ts +54 -0
- package/src/models/ai-receipt.ts +66 -0
- package/src/models/ai-routing-reward-request.ts +63 -0
- package/src/models/auto-automation-in.ts +6 -0
- package/src/models/auto-automation-patch.ts +6 -0
- package/src/models/auto-automation-run.ts +16 -1
- package/src/models/auto-automation.ts +6 -0
- package/src/models/auto-post-result.ts +42 -0
- package/src/models/auto-review-in.ts +42 -0
- package/src/models/git-workspace-view.ts +42 -0
- package/src/models/iam-user.ts +1 -1
- package/src/models/index.ts +14 -2
- package/src/models/provider-github-import-out.ts +6 -0
- package/src/models/provider-github-repo-item.ts +6 -0
- package/src/models/task-comment-list.ts +39 -0
- package/src/models/task-comment.ts +48 -0
- package/src/transport.ts +190 -0
- package/src/usage.ts +203 -0
- /package/dist/esm/models/{ai-decision-sides-false.js → ai-chat-in.js} +0 -0
- /package/dist/esm/models/{ai-decisions-choice-criteria-value.js → ai-chat.js} +0 -0
- /package/dist/models/{ai-decision-sides-false.js → ai-chat-in.js} +0 -0
- /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
|
+
|
package/src/models/iam-user.ts
CHANGED
|
@@ -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
|
*/
|
package/src/models/index.ts
CHANGED
|
@@ -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
|
+
|
package/src/transport.ts
ADDED
|
@@ -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
|
+
}
|
|
File without changes
|